DSL查询

  1. 叶子查询:在特定字段里查询特定值,属于简单查询,很少单独使用
  2. 复合查询:以逻辑方式组合多个叶子查询或更改叶子查询的行为方式
    • 在查询后还可以对查询结果做处理:
      • 排序:按照1个或多个字段做排序
      • 分页:根据from或size做分页,类似MySQL
      • 高亮:对搜索结果中的关键字添加特殊样式
      • 聚合:对搜索结果做数据统计以形成报表

基本语法

sql
GET /{索引库名}/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}

【例】:

sql
GET /items/_search 
{
"query": {
"match_all": {}
}
}

2b23b9d359714c4cb0f26918da897173.png

叶子查询

1. 全文检索查询

利用分词器对用户输入内容分词,然后去词条列表中匹配,默认按照匹配度排序。例如:match_query、multi_match_query

match查询(常用):会对用户输入的内容分词,然后去倒排索引检索,语法:

sql
GET /{索引库名}/_search
{
"query": {
"match": {
"字段名": "搜索条件"
}
}
}

【例】:搜索“脱脂牛奶”

sql
GET /items/_search
{
"query": {
"match": {
"name": "脱脂牛奶"
}
}
}

multi_match查询:与match查询类似,只不过允许查询多个字段,参与查询的字段越多,性能越差。语法:

sql
GET /{索引库名}/_search
{
"query": {
"multi_match": {
"query": "搜索条件",
"fields": ["字段1", "字段2"]
}
}
}

2. 精确查询

不对用户输入的内容做分词,直接精确匹配,一般是查找keyword、数值、日期、布尔等类型。例如:ids、range、term

term查询(常用)

sql
GET /{索引库名}/_search
{
"query": {
"term": {
"字段名": {
"value": "搜索条件"
}
}
}
}

【例】:查询“牛奶”分类下的商品

sql
GET /items/_search
{
"query": {
"term": {
"category": {
"value": "牛奶"
}
}
}
}

range查询

sql
GET /{索引库名}/_search
{
"query": {
"range": {
"字段名": {
"gte": {最小值},
"lte": {最大值}
}
}
}
}

【例】:查询价格≥5k,≤1w

sql
GET /items/_search
{
"query": {
"range": {
"price": {
"gte": 500000,
"lte": 1000000
}
}
}
}

ids查询
【例】:查询id为1861099和1861100的商品

sql
GET /items/_search
{
"query": {
"ids": {
"values": ["1861099", "1861100"]
}
}
}

3. 地理查询

用于搜索地理位置。例如:geo_distance、geo_bounding_box

复合查询

1. bool查询

基于逻辑运算组合叶子查询,实现组合条件,例如:bool

  1. must:必须匹配每个子查询(“与”)
  2. should:选择性匹配子查询(“或”)
  3. must_not:必须不匹配,不参与算分(“非”)
  4. filter:必须匹配,不参与算分(“与”)
sql
GET /索引库名/_search {
"query": {
bool查询条件: {
叶子查询
}
}
}

用户在输入框搜索“手机”,在底下:品牌选择“华为”,价格选择“1600以上元”。
bcceb581d0854273bcfb4d9b5a7bb18e.png

sql
GET /items/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "手机"
}
}
],
"filter": [
{
"term": {
"brand": "华为"
}
},
{
"range": {
"price": {
"gte": 160000
}
}
}
]
}
}
}

2. 算分函数查询

基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:function_score、dis_max

排序和分页

排序

es默认根据相关度算分(_score)来排序,也可以指定字段排序。可以排序的类型有:keyword、数值、地理坐标、日期。

sql
GET /索引库名/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"排序字段": {
"order": "排序方式asc和desc"
}
}
]
}

【例】:搜索“脱脂牛奶”,结果按照销量排序,销量一样按照价格升序排列

sql
GET /items/_search
{
"query": {
"match": {
"name": "脱脂牛奶"
}
},
"sort": [
{
"sold": "desc"
},
{
"price": "asc"
}
]
}

分页

es默认只返回前10的数据,如果查询更多数据就需要修改分页参数。

  • from:从第几个文档开始
  • size:总共查询几个文档
    【例】:搜索“脱脂牛奶”,查询出销量前10的商品,销量一样时按照价格升序
sql
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. 倒排索引在分词的时候,会把词条列表进行分词,还会记录词条在文档中的位置
3468afd108364447873d0a6fac332cba.png

sql
GET /{索引库名}/_search
{
"query": {
"match": {
"搜索字段": "搜索关键字"
}
},
"highlight": {
"fields": {
"高亮字段名称": {
"pre_tags": "<em>", // 高亮的前置标签
"post_tags": "</em>" // 高亮的后置标签
}
}
}
}

一般搜哪个字段,就对哪个字段做高亮,标签可以不加,默认是em

【例】:

sql
GET /items/_search
{
"query": {
"match": {
"name": "脱脂牛奶"
}
},
"highlight": {
"fields": {
"name": {}
}
}
}

4915936d29da416b9db298d7defdde97.png

JavaRestClient查询

基本语法

  1. 构建并发起请求
    64e8e6e4bb7f4e9fbf91386bb408a2cc.png
  2. 解析查询结果
    e0d930c0b734471ab2b87c60def123f7.png
java
@Test
void testSearch() throws IOException {
// 1. 创建Request对象
SearchRequest request = new SearchRequest("items");
// 2. 配置Request参数
request.source()
.query(QueryBuilders.matchAllQuery());
// 3. 发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4. 解析结果
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value; // 查询的总条数
SearchHit[] hits = searchHits.getHits(); // 查询的结果数组
for(SearchHit hit : hits) {
String json = hit.getSourceAsString();// 得到source
System.out.println(json);
}
}

叶子查询

全文检索查询

8e55b250e3064f519fd196741918f652.png

精确查询

884f81bd5b1d472f81992205b66c2daf.png

复合查询

布尔查询

a7c8fbc7b08e4c529d33aad057a93250.png
【例】:搜索关键字为“脱脂牛奶”,品牌为“德亚”,价格低于300元

java
@Test
void testSearch() throws IOException {
// 1. 创建Request对象
SearchRequest request = new SearchRequest("items");
// 2. 配置Request参数
request.source().query(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "脱脂牛奶"))
.filter(QueryBuilders.termQuery("brand", "德亚"))
.filter(QueryBuilders.rangeQuery("price").lt(30000))
);
// 3. 发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4. 解析结果
parseResponseResult(response); // 对上边解析response的步骤进行了封装
}

排序和分页

18b783395c2b4960bd2b2ba510271222.png

java
@Test 
void testSortAndPage() throws IOException {
SearchRequest request = new SearchRequest("items");
int pageNo = 1, pageSize = 5;
request.source().query(QueryBuilders.matchAllQuery()) // query条件
.from((pageNo - 1) * pageSize).size(pageSize) // 分页条件(分页开始的位置, 每页文档数量)
.sort("sold", SortOrder.DESC)
.sort("price", SortOrder.ASC); // 排序条件(排序字段, 排序方式)
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
parseResponseResult(response);
}

高亮显示

e3a3c7b588604fb496f34acb625fc0d9.png
高亮显示的结果解析:
c3e042b09f4c4baaa1fc3755d933deb1.png

java
@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()方法需要添加对高亮的处理:

java
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();// 得到source
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);
}
}

数据聚合

聚合可以实现对文档数据的统计、分析、运算,聚合常见的有:

  1. 桶聚合:用来对文档做分组
    • TermAggregation(term):按照文档字段值分组
    • Date Histogram:按照日期阶梯分组,例如:一周为一组,或一月为一组
  2. 度量聚合:用来计算一些值,如:最大值、最小值、平均值
    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum
  3. 管道聚合:其他聚合结果为基础做聚合,聚合的数据是其他聚合的结果

参与聚合的字段必须是Keyword、数值、日期、布尔类型的字段

DSL聚合

【例1】:统计所有商品中的商品分类

sql
# 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 // 希望获取的聚合结果数量
}
}
}
}

f29c201884a04b25be600954c26364a5.png
【例2】:统计手机的品牌,每个品牌价格的最小值、最大值、平均值

sql
GET /items/_search
{
"query": {
"term": {
"category": "手机"
}
},
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand"
},
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
}
}

4c0c108af89348e08fcc888e6cd7fd42.png

JavaRestClient聚合

6a085a6d89bd4516ac36f3e34ff2258a.png
解析聚合结果:
b71d01646c2842bfaf933f0048f6fa70.png

java
@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); // 这里用了向下转型(Aggregation:父、Terms:子)
// 获取buckets
List<? extends Terms.Bucket> buckets = terms.getBuckets();
// 遍历每一个bucket
for (Terms.Bucket bucket : buckets) {
System.out.println("brand:" + bucket.getKeyAsString());
System.out.println("count:" + bucket.getDocCount());
}
}