Scout 无法直接做 max/min 聚合,因其设计仅支持全文检索与简单过滤,aggs 不属于 query 上下文且 raw() 方法不解析 aggregations 字段,即使请求中包含 aggs,返回体中的聚合结果也被忽略或丢弃。

直接用 Laravel 的 Scout + Elasticsearch 驱动做 max / min 聚合,原生不支持——Scout 的 search() 和 raw() 都不透出 aggs 结构,必须绕过 Scout,直连 ES HTTP 接口或用官方 PHP 客户端。
为什么 Scout 无法直接做 max/min 聚合
Scout 的设计目标是「全文检索+简单过滤」,所有查询最终都编译为 query 上下文,aggs 是独立于 query 的顶层字段。即使你调用 Model::search(...)->raw(),返回的也是 hits 数据,aggregations 字段默认被丢弃或未解析。
常见错误现象:array_key_exists('aggregations', $response) 返回 false;或者 size: 0 后整个响应为空,误以为没数据。
- Scout 的
raw()方法只代理了_search的query和highlight等字段,不支持自定义aggs块 - 即使强行在
raw()参数里塞入aggs,Elasticsearch 会执行聚合,但 Scout 不解析返回体里的aggregations字段 -
size: 0是必须的(否则hits会占用带宽且干扰结果),但 Scout 默认至少返回 1 条hit
用 elasticsearch-php 客户端直连获取 max/min
推荐使用官方维护的 elasticsearch/elasticsearch 包(v8.x),它能精准控制请求体和响应解析,且兼容 ES 7.17+ 和 8.x。
实操建议:
- 安装:
composer require elasticsearch/elasticsearch - 初始化客户端时注意:ES 8.x 默认启用 HTTPS 和 API key 认证,
host要带https://,并传入api_key或basic_auth - 聚合请求必须设
"size": 0,否则aggregations仍会返回,但属于「额外开销」 - 字段类型必须是
keyword或数值型(integer,long,double),对text字段直接max会报错:illegal_argument_exception: Fielddata is disabled on text fields
示例(查 products 索引中 price 字段最大值):
$client = ClientBuilder::create()
->setHosts(['https://es.example.com:9200'])
->setApiKey(['id' => 'xxx', 'api_key' => 'yyy'])
->build();
$params = [
'index' => 'products',
'body' => [
'size' => 0,
'aggs' => [
'max_price' => ['max' => ['field' => 'price']],
'min_price' => ['min' => ['field' => 'price']],
],
],
];
$response = $client->search($params);
$max = $response['aggregations']['max_price']['value'] ?? null;
$min = $response['aggregations']['min_price']['value'] ?? null;
在 Laravel 中封装成可复用的聚合服务
避免每个地方都 new 客户端、拼数组。把连接、索引名、字段校验收拢到一个服务类里,同时处理常见异常。
关键点:
- 字段存在性检查:聚合前先用
GET /{index}/_mapping确认price字段 type 是数值型,不是text - 空结果容忍:
value可能为null(如全字段为null或无匹配文档),不要直接floatval() - 性能影响:单个
max聚合几乎无压力,但若高频调用(如每秒百次),要考虑加 Redis 缓存结果,TTL 设为 30–60 秒 - ES 7.0+ 开始,
TransportClient已废弃,Java 示例里的PreBuiltTransportClient完全不适用于当前 PHP 场景,别套用
容易被忽略的是:ES 的 max/min 对 null 值自动跳过,但如果你字段映射为 text 且开了 fielddata: true,它会尝试字符串比较(按字典序),结果完全不可信——必须确认 mapping。


















