DSL查询文档
## 1.DSL查询文档
1.1.DSL查询分类
## 1.1.DSL查询分类
Elasticsearch提供了基于JSON的DSL([Domain Specific Language](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html))来定义查询。常见的查询类型包括:
- **查询所有**:查询出所有数据,一般测试用。例如:match_all
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
- **全文检索(full text)查询**:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
- **精确查询**:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- range
- term
- **地理(geo)查询**:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
- **复合(compound)查询**:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score
- **全文检索(full text)查询**:
利用分词器对用户输入内容分词,然后去倒排索引库中匹配。
#全文检索查询的基本流程如下:
#- 对用户搜索的内容做分词,得到词条
#- 根据词条去倒排索引库中匹配,得到文档id
#- 根据文档id找到文档,返回给用户
例如:
- match_query
- multi_match_query
# 单字段查询 GET /hotel/_search { "query": { "match": { "city": "北京" } } } # 多字段查询,任意一个字段符合条件就算符合查询条件 # 参与查询字段越多,查询性能越差 GET /hotel/_search { "query": { "multi_match": { "query": "外滩", "fields": ["brand", "name", "business"] } } }
- **精确查询**:
根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
- range查询:根据数值范围查询,可以是数值、日期的范围
#term GET /hotel/_search { "query": { "term": { "city": { "value": "北京" } } } }
#range #这里的gte代表大于等于,gt则代表大于 #lte代表小于等于,lt则代表小于 GET /hotel/_search { "query": { "range": { "price": { "gte":100, "lte":300 } } } }
- **地理(geo)查询**:
根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
# geo_distance 附近查询 # 附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。 GET /hotel/_search { "query": { "geo_distance": { "distance": "15km", "location": "31.21,121.5" } } } # geo_bounding_box 矩形范围查询 GET /hotel/_search { "query":{ "geo_bounding_box":{ "location":{ "top_left":{ "lat":31.1, "lon":121.5 }, "bottom_right":{ "lat":30.9, "lon":121.7 } } } } }
- **复合(compound)查询**:
复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool -
function_score
function score 查询中包含四部分内容:
- **原始查询**条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,**原始算分**(query score)
- **过滤条件**:filter部分,符合该条件的文档才会重新算分
- **算分函数**:符合filter条件的文档要根据这个函数做运算,得到的**函数算分**(function score),有四种函数
- weight:函数结果是常量
- field_value_factor:以文档中的某个字段值作为函数结果
- random_score:以随机数作为函数结果
- script_score:自定义算分函数算法
- **运算模式**:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
- multiply:相乘
- replace:用function score替换query score
- 其它,例如:sum、avg、max、min
function score的运行流程如下:
- 1)根据**原始条件**查询搜索文档,并且计算相关性算分,称为**原始算分**(query score)
- 2)根据**过滤条件**,过滤文档
- 3)符合**过滤条件**的文档,基于**算分函数**运算,得到**函数算分**(function score)
- 4)将**原始算分**(query score)和**函数算分**(function score)基于**运算模式**做运算,得到最终结果,作为相关性算分。
因此,其中的关键点是:
- 过滤条件:哪些文档要加分
- 算分函数:如何计算function score
- 加权方式:function score 与 query score如何运算
GET /hotel/_search { "query":{ "function_score": { "query": {"match": { "all": "外滩" }}, "functions": [ { "filter": {"term":{"brand":"如家"}}, "weight": 10 } ], "boost_mode": "multiply" } } }
- TF-IDF算法
- BM25算法,elasticsearch5.1版本后采用的算法
TF-IDF算法有一各缺陷,就是词条频率越高,文档得分也会越高,单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限,曲线更加平滑:
布尔查询
布尔查询是一个或多个查询子句的组合,每一个子句就是一个**子查询**。子查询的组合方式有:
- must:必须匹配每个子查询,类似“与”
- should:选择性匹配子查询,类似“或”
- must_not:必须不匹配,**不参与算分**,类似“非”
- filter:必须匹配,**不参与算分**
GET /hotel/_search { "query":{ "bool":{ "must":[ {"term":{"city":"上海"}} ], "should":[ {"term":{"brand":"皇冠假日"}}, {"term":{"brand":"华美达"}} ], "must_not":[ {"range":{"price":{"lte":500}}} ], "filter":[ {"range":{"score":{"gte":45}}} ] } } }
##2.搜索结果处理
### 2.1.1.普通字段排序
keyword、数值、日期类型排序的语法基本一致。
**语法**:
GET /indexName/_search { "query": { "match_all": {} }, "sort": [ { "FIELD": "desc" // 排序字段、排序方式ASC、DESC } ] }
排序条件是一个数组,也就是可以写多个排序条件。按照声明的顺序,当第一个条件相等时,再按照第二个条件排序,以此类推。
### 2.1.2.地理坐标排序
GET /hotel/_search { "query":{ "match_all": {} }, "sort":{ "_geo_distance":{ "location": { "lat": "31.034661", "lon": "121.612282" }, "order":"asc", "unit":"km" } } }
这个查询的含义是:
- 指定一个坐标,作为目标点
- 计算每一个文档中,指定字段(必须是geo_point类型)的坐标 到目标点的距离是多少
- 根据距离排序
## 2.2.分页
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
- from:从第几个文档开始
- size:总共查询几个文档
类似于mysql中的`limit ?, ?`
### 2.2.1.基本的分页
分页的基本语法如下:
GET /hotel/_search { "query": { "match_all": {} }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 "sort": [ {"price": "asc"} ] }
现在,我要查询990~1000的数据,查询逻辑要这么写:
GET /hotel/_search { "query": { "match_all": {} }, "from": 990, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 "sort": [ {"price": "asc"} ] }
这里是查询990开始的数据,也就是 第990~第1000条 数据。
不过,elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条:
查询TOP1000,如果es是单点模式,这并无太大影响。
但是elasticsearch将来一定是集群,例如我集群有5个节点,我要查询TOP1000的数据,并不是每个节点查询200条就可以了。
因为节点A的TOP200,在另一个节点可能排到10000名以外了。
因此要想获取整个集群的TOP1000,必须先查询出每个节点的TOP1000,汇总结果后,重新排名,重新截取TOP1000。
那如果我要查询9900~10000的数据呢?是不是要先查询TOP10000呢?那每个节点都要查询10000条?汇总到内存中?
当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求。
针对深度分页,ES提供了两种解决方案,[官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html):
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
- scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。
# - search after 例子:
GET /hotel/_search { "query": { "match_al": {} }, "size": 2, "from": 0, "search_after": [ 135,45 ], "sort": [ {"price": "asc"}, {"score": "asc"} ] }
### 2.2.3.小结
分页查询的常见实现方案以及优缺点:
- `from + size`:
- 优点:支持随机翻页
- 缺点:深度分页问题,默认查询上限(from + size)是10000
- 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
- `after search`:
- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:只能向后逐页查询,不支持随机翻页
- 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
- `scroll`:
- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:会有额外内存消耗,并且搜索结果是非实时的
- 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。
## 2.3.高亮
### 2.3.1.高亮原理
什么是高亮显示呢?
我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示:
高亮显示的实现分为两步:
- 1)给文档中的所有关键字都添加一个标签,例如`<em>`标签
- 2)页面给`<em>`标签编写CSS样式
### 2.3.2.实现高亮
**高亮的语法**:
```json
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": {
"pre_tags": "<em>", // 用来标记高亮字段的前置标签
"post_tags": "</em>" // 用来标记高亮字段的后置标签
}
}
}
}
```
GET /hotel/_search { "query": { "match": { "all": "如家" } }, "size": 2, "from": 0, "highlight": { "fields": { "name": { "require_field_match": "false" } } } }
**注意:**
- 高亮是对关键字高亮,因此**搜索条件必须带有关键字**,而不能是范围这样的查询。
- 默认情况下,**高亮的字段,必须与搜索指定的字段一致**,否则无法高亮
- 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
##3.聚合查询
### 3.1.1.聚合的种类
聚合常见的有三类:
- **桶(Bucket)**聚合:用来对文档做分组
- TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
- **度量(Metric)**聚合:用以计算一些值,比如:最大值、最小值、平均值等
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同时求max、min、avg、sum等
- **管道(pipeline)**聚合:其它聚合的结果为基础做聚合
> **注意:**参加聚合的字段必须是keyword、日期、数值、布尔类型
## 3.1.2.DSL实现聚合
现在,我们要统计所有数据中的酒店品牌有几种,其实就是按照品牌对数据分组。此时可以根据酒店品牌的名称做聚合,也就是Bucket聚合。
### 3.2Bucket聚合语法
语法如下:
GET /hotel/_search { "size":0, "query":{ "term": {"city":"北京"} }, "aggs":{ "brandAgg": { "terms":{ "field":"brand", "size":20, "order":{ "_count": "asc" } } } } }
### 3.3Metric聚合语法
上节课,我们对酒店按照品牌分组,形成了一个个桶。现在我们需要对桶内的酒店做运算,获取每个品牌的用户评分的min、max、avg等值。
这就要用到Metric聚合了,例如stat聚合:就可以获取min、max、avg等结果。
语法如下:
```json GET /hotel/_search { "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "size": 20 }, "aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算 "score_stats": { // 聚合名称 "stats": { // 聚合类型,这里stats可以计算min、max、avg等 "field": "score" // 聚合字段,这里是score } } } } } }
这次的score_stats聚合是在brandAgg的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算。
另外,我们还可以给聚合结果做个排序,例如按照每个桶的酒店平均分做排序:
- 限定聚合的的文档范围
聚合必须的三要素:
- 聚合名称
- 聚合类型
- 聚合字段
聚合可配置属性有:
- size:指定聚合结果数量
- order:指定聚合结果排序方式
- field:指定聚合字段