ElasticSearch下
|总字数:2.8k|阅读时长:11分钟|浏览量:|
DSL查询
- 叶子查询:在特定字段里查询特定值,属于简单查询,很少单独使用
- 复合查询:以逻辑方式组合多个叶子查询或更改叶子查询的行为方式
- 在查询后还可以对查询结果做处理:
- 排序:按照1个或多个字段做排序
- 分页:根据from或size做分页,类似MySQL
- 高亮:对搜索结果中的关键字添加特殊样式
- 聚合:对搜索结果做数据统计以形成报表
基本语法
GET /{索引库名}/_search { "query": { "查询类型": { "查询条件": "条件值" } } }
|
【例】:
GET /items/_search { "query": { "match_all": {} } }
|

叶子查询
1. 全文检索查询
利用分词器对用户输入内容分词,然后去词条列表中匹配,默认按照匹配度排序。例如:match_query、multi_match_query
match查询(常用)
:会对用户输入的内容分词,然后去倒排索引检索,语法:
GET /{索引库名}/_search { "query": { "match": { "字段名": "搜索条件" } } }
|
【例】:搜索“脱脂牛奶”
GET /items/_search { "query": { "match": { "name": "脱脂牛奶" } } }
|
multi_match查询
:与match查询类似,只不过允许查询多个字段,参与查询的字段越多,性能越差。语法:
GET /{索引库名}/_search { "query": { "multi_match": { "query": "搜索条件", "fields": ["字段1", "字段2"] } } }
|
2. 精确查询
不对用户输入的内容做分词,直接精确匹配,一般是查找keyword、数值、日期、布尔等类型。例如:ids、range、term
term查询(常用)
:
GET /{索引库名}/_search { "query": { "term": { "字段名": { "value": "搜索条件" } } } }
|
【例】:查询“牛奶”分类下的商品
GET /items/_search { "query": { "term": { "category": { "value": "牛奶" } } } }
|
range查询
:
GET /{索引库名}/_search { "query": { "range": { "字段名": { "gte": {最小值}, "lte": {最大值} } } } }
|
【例】:查询价格≥5k,≤1w
GET /items/_search { "query": { "range": { "price": { "gte": 500000, "lte": 1000000 } } } }
|
ids查询
:
【例】:查询id为1861099和1861100的商品
GET /items/_search { "query": { "ids": { "values": ["1861099", "1861100"] } } }
|
3. 地理查询
用于搜索地理位置。例如:geo_distance、geo_bounding_box
复合查询
1. bool查询
基于逻辑运算组合叶子查询,实现组合条件,例如:bool
- must:必须匹配每个子查询(“与”)
- should:选择性匹配子查询(“或”)
- must_not:必须不匹配,不参与算分(“非”)
- filter:必须匹配,不参与算分(“与”)
GET /索引库名/_search { "query": { bool查询条件: { 叶子查询 } } }
|
用户在输入框搜索“手机”,在底下:品牌选择“华为”,价格选择“1600以上元”。

GET /items/_search { "query": { "bool": { "must": [ { "match": { "name": "手机" } } ], "filter": [ { "term": { "brand": "华为" } }, { "range": { "price": { "gte": 160000 } } } ] } } }
|
2. 算分函数查询
基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:function_score、dis_max
排序和分页
排序
es默认根据相关度算分(_score)来排序,也可以指定字段排序。可以排序的类型有:keyword、数值、地理坐标、日期。
GET /索引库名/_search { "query": { "match_all": {} }, "sort": [ { "排序字段": { "order": "排序方式asc和desc" } } ] }
|
【例】:搜索“脱脂牛奶”,结果按照销量排序,销量一样按照价格升序排列
GET /items/_search { "query": { "match": { "name": "脱脂牛奶" } }, "sort": [ { "sold": "desc" }, { "price": "asc" } ] }
|
分页
es默认只返回前10的数据,如果查询更多数据就需要修改分页参数。
- from:从第几个文档开始
- size:总共查询几个文档
【例】:搜索“脱脂牛奶”,查询出销量前10的商品,销量一样时按照价格升序
GET /items/_search { "query": { "match": { "name": "脱脂牛奶" } }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 每页文档数量,默认10 "sort": [ { "sold": "desc" }, { "price": "asc" } ] }
|
深度分页问题
es中from + size不能超过1w条,因为太深了会有深度分页问题。
【产生原因】因为es存储的数据很多,所以es数据一般会采用分片存储,把一个索引中的数据分成N份,存储到不同的节点上。查询时需要汇总各个分片的数据。查询的页码越深,从每个分片差的数据量越多,内存压力越大,性能越差。
【解决办法】search after模式:分页时需要排序
,原理是在上一次排序后,会记住上一次的排序值,下一次排序时,就会直接从上一次排序值开始,查询下一页数据。
- 优点:没有查询上限,支持深度分页
- 缺点:只能向后逐页查询,不能随即翻页
- 场景:数据迁移,手机滚动查询
高亮显示
在搜索结果中,把搜索结果突出显示
【原理】:
1. 高亮词条都加了<em>
标签,标签上都添加了红色样式
2. 倒排索引在分词的时候,会把词条列表进行分词,还会记录词条在文档中的位置

GET /{索引库名}/_search { "query": { "match": { "搜索字段": "搜索关键字" } }, "highlight": { "fields": { "高亮字段名称": { "pre_tags": "<em>", // 高亮的前置标签 "post_tags": "</em>" // 高亮的后置标签 } } } }
|
一般搜哪个字段,就对哪个字段做高亮,标签可以不加,默认是em
【例】:
GET /items/_search { "query": { "match": { "name": "脱脂牛奶" } }, "highlight": { "fields": { "name": {} } } }
|

JavaRestClient查询
基本语法
- 构建并发起请求

- 解析查询结果

@Test void testSearch() throws IOException { SearchRequest request = new SearchRequest("items"); request.source() .query(QueryBuilders.matchAllQuery()); SearchResponse response = client.search(request, RequestOptions.DEFAULT); SearchHits searchHits = response.getHits(); long total = searchHits.getTotalHits().value; SearchHit[] hits = searchHits.getHits(); for(SearchHit hit : hits) { String json = hit.getSourceAsString(); System.out.println(json); } }
|
叶子查询
全文检索查询

精确查询

复合查询
布尔查询

【例】:搜索关键字为“脱脂牛奶”,品牌为“德亚”,价格低于300元
@Test void testSearch() throws IOException { SearchRequest request = new SearchRequest("items"); request.source().query( QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("name", "脱脂牛奶")) .filter(QueryBuilders.termQuery("brand", "德亚")) .filter(QueryBuilders.rangeQuery("price").lt(30000)) ); SearchResponse response = client.search(request, RequestOptions.DEFAULT); parseResponseResult(response); }
|
排序和分页

@Test void testSortAndPage() throws IOException { SearchRequest request = new SearchRequest("items"); int pageNo = 1, pageSize = 5; request.source().query(QueryBuilders.matchAllQuery()) .from((pageNo - 1) * pageSize).size(pageSize) .sort("sold", SortOrder.DESC) .sort("price", SortOrder.ASC); SearchResponse response = client.search(request, RequestOptions.DEFAULT); parseResponseResult(response); }
|
高亮显示

高亮显示的结果解析:

@Test void testHighLight() throws IOException { SearchRequest request = new SearchRequest("items"); request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶")) .highlighter(SearchSourceBuilder.highlight().field("name")); SearchResponse response = client.search(request, RequestOptions.DEFAULT); parseResponseResult(response); }
|
由于高亮的结果不是在source里的,所以parseResponseResult()方法需要添加对高亮的处理:
private static void parseResponseResult(SearchResponse response) { SearchHits searchHits = response.getHits(); long total = searchHits.getTotalHits().value; SearchHit[] hits = searchHits.getHits(); for(SearchHit hit : hits) { String json = hit.getSourceAsString(); ItemDoc itemDoc = BeanUtil.copyProperties(json, ItemDoc.class);
Map<String, HighlightField> hfs = hit.getHighlightFields(); if(hfs != null && !hfs.isEmpty()) { HighlightField hf = hfs.get("name"); String hfName = hf.getFragments()[0].string(); itemDoc.setName(hfName); } System.out.println(itemDoc); } }
|
数据聚合
聚合可以实现对文档数据的统计、分析、运算,聚合常见的有:
桶聚合
:用来对文档做分组
- TermAggregation(term):按照文档字段值分组
- Date Histogram:按照日期阶梯分组,例如:一周为一组,或一月为一组
度量聚合
:用来计算一些值,如:最大值、最小值、平均值
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同时求max、min、avg、sum
管道聚合
:其他聚合结果为基础做聚合,聚合的数据是其他聚合的结果
参与聚合的字段必须是Keyword、数值、日期、布尔类型的字段
DSL聚合
【例1】:统计所有商品中的商品分类
# select count(1) "categroyAgg" from items group by category GET /items/_search { "query": {"match_all": {}}, // 如果使用"match_all",可以省略 "size": 0, // 如果不设置size,默认为10,不仅会返回聚合结果,还会返回搜索结果,增加网络传输的负担 "aggs": { // 定义聚合 "categroyAgg": { // 给聚合起个名字 "terms": { // 聚合的类型,按照分类聚合,所以选择term "field": "category", // 参与聚合的字段 "size": 5 // 希望获取的聚合结果数量 } } } }
|

【例2】:统计手机的品牌,每个品牌价格的最小值、最大值、平均值
GET /items/_search { "query": { "term": { "category": "手机" } }, "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand" }, "aggs": { "price_stats": { "stats": { "field": "price" } } } } } }
|

JavaRestClient聚合

解析聚合结果:

@Test void testAgg() throws IOException { SearchRequest request = new SearchRequest("items"); String brandAggName = "brandAgg"; request.source() .size(0) .aggregation(AggregationBuilders.terms(brandAggName) .field("brand") .size(20) ); SearchResponse response = client.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); Terms terms = aggregations.get(brandAggName); List<? extends Terms.Bucket> buckets = terms.getBuckets(); for (Terms.Bucket bucket : buckets) { System.out.println("brand:" + bucket.getKeyAsString()); System.out.println("count:" + bucket.getDocCount()); } }
|