搜索结果处理

排序

默认根据相关度分数排序, 可以排序的类型分为keyword, 数值类型, 地理坐标, 日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /idxName/_search
{
"query": {
"match_all": {},
"sort": [
{
"FIELD": "desc"
},
{
"_geo_distance": {
"FIELD": "<lat, lon>",
"order": "asc",
"unit": "km"
}
}
]
}
}

分页

默认只会返回10条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /idxName/_search
{
"query": {
"match_all": {}
},
"from": 990,
"size": 10,
"sort": [
{
"price": "asc"
}
]
}
// from 文档开始位置
// size 文档总数
  • 由于ES是倒排索引, 因此分页非常困难, 本身分页是逻辑分页
  • 比如查询990开始的10条数据, 实际上是ES查询了0-1000的所有数据, 然后截取了其中的990-1000的部分
  • 单点ES没有问题, 但是如果是集群, ES中会将数据分片
  • 因此需要将每个分片上的前1000, 然后再合并汇总, 重新排序得到前1000, 最后截取990-1000的部分
  • 所以如果搜索页数过多, 或者结果集过深(from + size)过大, 资源消耗过多, 所以限制了from+size<10000from + size < 10000
  • 官方给出两个解决办法:
    • search after 分页时需要排序, 从上一次排序值开始, 查询下一页数据, 但是只能往后查询, 不能往前查询
    • scroll: 将排序数据形成快照, 保存在内存中, 官方现在不推荐用, 对内存消耗较大, 并且快照更新无法实时

高亮

搜索结果突出显示, 提前用标签标记, 然后对标签添加css样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /idxName/_search
{
"query": {
"match": {
"FIELD": "TEXT"
}
},
"highlight": {
"fields": {
"FIELD": {
"require_field_match": "false",
"pre_tags": "<em>",
"posts_tags": "</em>"
}
}
}
}
// FIELD 表示需要高亮的字段
// pre_tags表示关键字之前的标签
// 不能使用match_all, 因为需要关键字了
// 默认情况下, 搜索字段必须要和高亮字段一致, 也就是两个FIELD需要相同
// 使用require_field_match = false可以解决这个问题

RestClient查询文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void MatchAll() throws IOException {
// 1. 准备Request
SearchRequest req = new SearchRequest("xxx");
// 2. 准备DSL
req.source().query(QueryBuilders.matchAllQuery());
// 2.1 排序
req.source().sort("xxx", SortOrder.ASC);
// 2.2 分页
req.source().from(0).size(5);
// 3. 返回值
SearchResponse resp = client.search(req, RequestOptions.DEFAULT);
// 4. 解析结果
SearchHits hits = resp.getHits();
// 4.1 获得总数
long total = hits.getTotalHits().value;
// 4.2 查询结果数组
SearchHit[] h = hits.getHits();
for (SearchHit hh : h) {
// 4.3 得到source
String json = hh.getSourceAsString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Highlight() throws IOException {
SearchRequest req = new SearchRequest("xxx");
req.source().query(QueryBuilders.matchQuery("all", "xxxx"));
req.source().highlighter(new HighlightBuilder().field("xxx").requireFieldMatch(false));
SearchResponse resp = client.search(req, RequestOptions.DEFAULT);
// ... 处理得到hit
// 反序列化获取source
HotelDoc hD = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
// 处理高亮
Map<String, HighlightField> hF = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(hF)) {
// 获取高亮字段结果
HighlightField hlF = hF.get("xxx");
if (hlF != null) {
// 去除高亮结果数组中的第一个, 就是需要的东西
String name = hlF.getFragments()[0].string();
// 覆盖非高亮结果
hD.setName(name);
}
}
}

数据聚合

实现对文档数据的统计, 分析, 运算, 不能对Text进行聚合

种类

桶聚合(Bucket)

对文档分组, 默认是对所有文档进行搜索, 限定聚合范围可以使用query条件

  1. TermAggregation: 按照文档字段值分组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /idxName/_search
{
"query": {
"range": {
"price": {
"lte": 200 // 限定聚合范围
}
}
},
"size": 0, // 设置size = 0, 结果不包含文档, 只包含聚合结果
"aggs": { // 定义聚合
"brandAgg": { // 给聚合起名字
"terms": { // 聚合的类型, 按照品牌值进行聚合, 所以选择term
"field": "brand", // 参与聚合的字段
"size": 20 // 希望获取的聚合结果数量
}
}
}
}
  1. Date Histogram: 按照日期阶梯分组, 比如一周一组

度量聚合(Metric)

  1. Avg: 平均
  2. Max: 最大
  3. Min: 最小
  4. Stats: 同时求min, max, avg, sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
GET /idxName/_search
{
"query": {
"range": {
"price": {
"lte": 200 // 限定聚合范围
}
}
},
"size": 0, // 设置size = 0, 结果不包含文档, 只包含聚合结果
"aggs": { // 定义聚合
"brandAgg": { // 给聚合起名字
"terms": { // 聚合的类型, 按照品牌值进行聚合, 所以选择term
"field": "brand", // 参与聚合的字段
"size": 20 // 希望获取的聚合结果数量
"order": {
"scoreAgg.avg": "desc"
}
},
"aggs": { // 对桶聚合的结果进行统计
"scoreAgg": {
"stats": {
"field": "score"
}
}
}
}
}
}

管道聚合(pipeline)

其他聚合的结果为基础进行聚合