3.3、干货!32个查询案例!#
下面一起看一下有哪些query dsl的使用方式。(查询的返回值和上面我们一起看的那个是一样的,所以下面的重点是怎么查,而不是怎么看返回值哈)
1、查询指定index下的全部doc
# _search是关键字,下文基本每个查询都会有它,不再赘述了哈 GET /your_index/your_type/_search { "query": { "match_all": {} } }
2、针对name字段进行全文检索(match查询)
ES会将用户将输入的字符串通过分词器拆解开,然后去倒排索引中扫描匹配(下一篇文章白日梦的笔记会重新杀回ES涉及的核心概念,包括这个倒排索引)。在倒排索引中哪怕匹配上了一个也会将结果返回。
GET /yourIndex/yourType/_search { "query": { # match表示全文检索,所以白日梦会被分词成 白日、梦、白日梦 # 也就是说当前的match会匹配出name中有“白日” 或者“梦” 或者“白日梦”的doc "match": { "name":"白日梦" } } } # 实际上,match query底层会被转换成下面的格式进行检索 # # { # "bool":{ # "should":[ # {"term":{"title":"白日"}}, # {"term":{"title":"白日梦"}}, # {"term":{"title":"梦"}} # ] # } # } #
3、全文检索:手动控制全文检索的精度
GET /your_index/your_type/_search { "query": { "match": { "name":{ "query":"bairi meng", # and表示,只有同时出现bairi meng两个词的doc才会被命中 # 如果不加and限制,则bairi和meng之间是或的关系,只要出现一个就行 "operator":"and", } } } } # 添加上operator 操作会被ES转换成下面的格式,将上面的should转换成must # # { # "bool":{ # "must":[ # {"term":{"title":"bairi"}}, # {"term":{"title":"meng"}} # ] # } # }
4、去掉全文检索的长尾
# 去长尾 GET /your_index/your_type/_search { "query": { "match": { "name":{ "query":"欢迎关注白日梦!", "operator":"and", # 上面的query可能被分词成: 欢迎、关注、白日梦、欢迎关注、关注白日梦这五个词。 # 默认来说只要命中其中的一个词,那个doc就会被返回,所以有长尾现象。 # 去长尾:控制至少命中3/4个词的doc才算是真正命中。 "minimum_should_match":"75%" } } } } # 添加上 minimum_should_match 操作会被ES转换成下面的格式 # # { # "bool":{ # "should":[ # {"term":{"title":"白日"}}, # {"term":{"title":"梦"}} # ], # "minimum_should_match":3 # } # } #
5、全文检索:通过boost控制权重。
如下Case:要求doc的name字段必须包含:“关注”,于此同时,如果doc的name字段中包含:“白日梦”,则将这个doc的权重提高为3,如果name字段中包含了“公众号” 再提高它的权重2。经过这样的处理,name字段中包含:“关注白日梦公众号” 的doc的权重就最高,它在搜索结果中的排名就越靠前。
GET /your_index/your_type/_search { "query": { "bool":{ "must":{ "match": { "name":{ # 默认情况下,所有字段的权重都是样的,都是1 "query":"关注", } } }, "should":[ { "match": { "name":{ "query":"白日梦", # 将name字段的权重提升成3 "boost":3 } } }, { "match": { "name":{ "query":"公众号", # 将name字段的权重提升成3 # 默认情况下,所有字段的权重都是样的,都是1 "boost":2 } } } ] } } }
6、稍微复杂一点的多条件查询:bool查询
GET /your_index/your_type/_search { "query": { # 比如你的查询比较复杂,涉及到很多的子查询,那你可以考虑通过bool查询包裹这些子查询 # 每一个子查询都会计算出这个doc针对于它这种查询得到的相关性得分。 # 最终由bool查询将这些得分合并为一个最终的得分 "bool": { # 必须匹配到XXX, 并且会得出相关性得分 # address中必须包含mill "must": [ {"match": { "address": "mill" } }, ], # 在满足must的基础上,should条件不满足也可以,但是如果也匹配上了,相关性得分会增加 # 如果没有must的话,should中的条件必须满足一个 "should": [{ "match": { "address": "lane" } }], "must_not": [ # 一定不包含谁 { "match": { "address": "mill" } }, ] } } }
7、bool查询+去长尾。
# bool查询+去长尾 GET /your_index/your_type/_search { "query": { "bool":{ "should":[ "match":{"name":"白日梦1"}, "match":{"name":"白日梦2"}, "match":{"name":"白日梦3"}, ], "minimum_should_match":3 } } }
8、best fields策略:取多个query中得分最高的得分作为doc的最终得分。
一个query中是存在多个match的(我们称它为多字段查询),而且每个match都会贡献自己的相关性得分,也就是说doc最终的相关性得分是通过这多个match贡献的相关性得分通过一定的机制计算出来的。而且相关性得分越高,文档在搜索结果中就越靠前。
这时,如果你不希望让doc的最终得分是通过综合所有的match计算得出的,可以使用dis_max查询。它会取所有match中得分最高的match当作doc的最终得分。
GET /your_index/your_type/_search { "query": { # 这种用法不容忽略 # 直接取下面多个query中得分最高的query当成最终得分 "dis_max": { "queries":[ {"match":{"name":"白日梦"}}, {"match":{"content":"关注白日梦!"}} ] } } }
9、基于 tie_breaker 优化dis_max
上面的Case中有提到这个dis_max查询,这个dis_max也是实现best field的关键,即:它会取所有match中得分最高的match当作doc的最终得分。
而这个例子中的tie_breaker会重新让dis_max考虑到其他field的得分影响,比如下面的0.4,表示最终的doc得分会考虑其他match的影响,但是它的影响会被弱化成原来的0.4。
GET /your_index/your_type/_search { # 基于 tie_breaker 优化dis_max # tie_breaker可以使dis_max考虑其它field的得分影响 "query": { # 直接取下面多个query中得分最高的query当成最终得分 # 这也是best field策略 "dis_max": { "queries":[ {"match":{"name":"关注"}}, {"match":{"content":"白日梦"}} ], "tie_breaker":0.4 } } }
10、同时在你指定的多个字段中进行检索:multi_match
GET /your_index/your_type/_search { # 查询多个,在下面指定的两个字段中检索含有 “this is a test“ 的doc "query": { "multi_match" : { "query": "this is a test", "fields": [ "subject", "message" ] } } }
11、使用multi_match query简化dis_max
# 还是这个dis_max query,如下: GET /your_index/your_type/_search { # 基于 tie_breaker 优化dis_max # tie_breaker可以使dis_max考虑其它field的得分影响 "query": { # 直接取下面多个query中得分最高的query当成最终得分 # 这也是best field策略 "dis_max": { "queries":[ {"match":{"name":"关注"}}, {"match":{"content":"白日梦"}} ], "tie_breaker":0.4 } } } # 使用multi_match query简化写法如下: GET /your_index/your_type/_search { "query": { "multi_match":{ "query":"关注 白日梦", # 指定检索的策略 best_fields(因为dis_max就是best field策略) "type":"best_fields", # content^2 表示增加权重,相当于:boost2 "fields":["name","content^2"], "tie_breaker":0.4, "minimum_should_match":3 } } }
12、most field策略和上面说的best field策略是不同的,因为best field策略说的是:优先返回某个field匹配到更多关键字的doc。
优先返回有更多的field匹配到你给定的关键字的doc。而不是优先返回某个field完全匹配你给定关键字的doc
另外most_fields不支持使用minimum_should_match
去长尾。
GET /your_index/your_type/_search { # most_fields策略、优先返回命中更多关键词的doc # 如下从title、name、content中搜索包含“赐我白日梦”的doc "query": { "multi_match":{ "query":"赐我白日梦", # 指定检索的策略most_fields "type":"most_fields", "fields":["title","name","content"] } } }
13、cross_fields策略:如下Case
GET /your_index/your_type/_search { "query": { "multi_match":{ "query":"golang java", # cross_fields 要求golang:必须在title或者在content中出现 # cross_fields 要求java:必须在title或者在content中出现 "type":"cross_fields", "fields":["title","content"] } } }
14、查询空
GET /your_index/your_type/_search { "query": { "match_none": {} } }
15、精确匹配
# 使用trem指定单个字段进行精确匹配 GET /your_index/your_type/_search { # 精确匹配name字段为白日梦的doc "query": { "constant_score":{ "filter":{ "term": { "name":"白日梦" } } } } } # 使用terms指定在多个字段中进行精确匹配 # 下面的例子相当于SQL: where name in ('tom','jerry') GET /your_index/your_type/_search { # 精确匹配 "query": { "constant_score":{ "filter":{ "terms": { "想搜索的字段名":[ "tom", "jerry" ] } } } } }
16、短语检索:要求doc的该字段的值和你给定的值完全相同,顺序也不能变,所以它的精确度很高,但是召回率低。
GET /your_index/your_type/_search { # 短语检索 # 顺序的保证是通过 term position来保证的 # 精准度很高,但是召回率低 "query": { # 只有name字段中包含了完整的 白日梦 这个doc才算命中 # 不能是单个 ”白日“,也不能是单个的 “梦”,也不能是“白日xxx梦” # 要求 短语相连,且顺序也不能变 "match_phrase": { "name": "白日梦" } } }
17、提高短语检索的召回率
如果使用match_phase进行短语检索,本质上就是要求doc中的字段值和给定的值完全相同,即使是顺序不同也不行。但是为了提高召回率如你又想容忍短语匹配可以存在一定的误差,比如你希望搜索 “i love world” 时,能够搜索出''world love i"
这时可以通过slop来实现这个功能,slop可以帮你让指定短语中的词最多经过slop次移动后如果能匹配某个doc,也把这个doc当作结果返回给用户。
GET /your_index/your_type/_search { # 短语检索 "query": { # 指定了slop就不再要求搜索term之间必须相邻,而是可以最多间隔slop距离。 # 在指定了slop参数的情况下,离关键词越近,移动的次数越少, relevance score 越高。 # match_phrase + slop 和 proximity match 近似匹配作用类似。 # 平衡精准度和召回率。 "match_phrase": { "address": "mill lane", # 指定搜索文本中的几个term经过几次移动后可以匹配到一个doc "slop":2 } } }
18、混合使用match和match_phrase 平衡精准度和召回率
GET /your_index/your_type/_search { # 混合使用match和match_phrase 平衡精准度和召回率 "query": { "bool": { "must": { # 全文检索虽然可以匹配到大量的文档,但是它不能控制词条之间的距离 # 可能i love world在doc1中距离很近,但是它却被ES排在结果集的后面 # 它的性能比match_phrase和proximity高 "match": { "title": "i love world" } }, "should": { # 因为slop有个特性:词条之间间隔的越近,移动的次数越少 最终的得分就越高 # 于是可以借助match_phrase+slop感知term position的功能 # 实现为距离相近的doc贡献分数,让它们靠前排列 "match_phrase":{ "title":{ "query":"i love world", "slop":15 } } } } }
19、使用rescore_query重打分。提高精准度和召回率。
GET /your_index/your_type/_search { # 重打分机制 "query": { "match":{ "title":{ "query":"i love world", "minimum_should_match":"50%" } }, # 对全文检索的结果进行重新打分 "rescore":{ # 对全文检索的前50条进行重新打分 "window_size":50, "query": { # 关键字 "rescore_query":{ # match_phrase + slop 感知 term persition,贡献分数 "match_phrase":{ "title":{ "query":"i love world", "slop":50 } } } } } }
20、前缀匹配:搜索 user字段以"白日梦"开头的 doc
GET /your_index/your_type/_search { # 前缀匹配,相对于全文检索,前缀匹配是不会对前缀进行分词的。 # 而且每次匹配都会扫描整个倒排索引,直到扫描完一遍才会停下来 # 前缀搜索不会计算相关性得分所有的doc的得分都是1 # 前缀越短能匹配到的doc就越多,性能越不好 "query": { "prefix" : { "user" : "白日梦" } } }
21、前缀搜索 + 添加权重
GET /your_index/your_type/_search { # 前缀搜索 + 添加权重 "query": { "prefix" : { "name" : { "value" : "白日梦", "boost" : 2.0 } } } }
22、通配符搜索
GET /your_index/your_type/_search { # 通配符搜索 "query": { "wildcard" : { "title" : "白日梦的*笔记" } } } GET /your_index/your_type/_search { # 通配符搜索 "query": { "wildcard" : { "title" : { "value" : "白日梦的*笔记", "boost" : 2.0 } } } }
23、正则搜索
GET /your_index/your_type/_search { # 正则搜索 "query": { "regexp":{ "name.first":{ "value":"s.*y", "boost":1.2 } } } }
24、搜索推荐:match_phrase_prefix,最终实现的效果类似于百度搜索,当用户输入一个词条后,将其它符合条件的词条的选项推送出来。
match_phrase_prefix和match_phrase相似,但是区别是它会将最后一个term当作前缀,发起一次搜索。因此它也叫search time 搜索推荐,因为它是在你搜索的时候又发起了一次新的请求来拿到推荐的内容,它的效率整体也是比较低的。
GET /your_index/your_type/_search { "query": { # 前缀匹配(关键字) "match_phrase_prefix" : { "message" : { # 比如你搜索关注白日梦,经过分词器处理后会得到最后一个词是:“白日梦” # 然后他会拿着白日梦再发起一次搜索,于是你就可能搜到下面的内容: # “关注白日梦的微信公众号” # ”关注白日梦的圈子“ "query" : "关注白日梦", # 指定前缀最多匹配多少个term,超过这个数量就不在倒排索引中检索了,提升性能 "max_expansions" : 10, # 提高召回率,使用slop调整term persition,贡献得分 "slop":10 } } } }
25、Function Score Query
Function Score Query 实际上是一种让用户可以自定义实现一种对doc得分进行增强的手段。比如:用户可以自定义一个function_secore 函数,然后指定将这个field的值和ES计算出来的分数相乘,作为doc的最终得分。
# Case1 GET /your_index/your_type/_search { "query": { "function_score": { # 正常写一个query "query": { "match": { "query":"es" } }, # 自定义增强策略 “field_value_factor”:{ # 对检索出的doc的最终得分都要multiply上star字段的值 "field":"star", } "boost_mode":"multiply", # 限制最大的得分不能超过maxboost指定的值。 "maxboost":3 } } } # Case2 GET /your_index/your_type/_search { "query": { "function_score": { "query": { "match": { "query":"es" } }, “field_value_factor”:{ # 对检索出的doc的最终得分都要multiply上star字段的值 # 这时有个问题,假如说star字段的值为0,那最终结果岂不是都为0? "field":"star", # 所以考虑使用modifier优化一下 # newScore = oldScore + log(1+star) "modifier":"log1p", } "boost_mode":"multiply", "maxboost":3 } } } # Case3 GET /your_index/your_type/_search { "query": { "function_score": { "query": { "match": { "query":"es" } }, “field_value_factor”:{ "field":"star", "modifier":"log1p", # 使用factor将star字段对权重的影响降低成1/10 # newScore = oldScore + log( 1 + star*factor ) "factor":0.1 } "boost_mode":"multiply", "maxboost":3 } } } # 补充boost_mode有哪些中选项 multiply、sum、min、max、replace
26、Fuzzy Query 模糊查询会提供容错的处理
GET /your_index/your_type/_search { # Fuzzy Query 模糊查询会提供容错的处理 "query": { "fuzzy" : { "user" : { "value": "白日梦", "boost": 1.0, # 最大的纠错次数,一般设为之AUTO "fuzziness": 2, # 不会被“模糊化”的初始字符数。这有助于减少必须检查的术语的数量。默认值为0。 "prefix_length": 0, # 模糊查询将扩展到的最大项数。默认值为50 "max_expansions": 100 # 是否支持模糊变换(ab→ba)。默认的是false transpositions:true } } } }
27、解读一个实用的案例
GET /your_index/your_type/_search { "query": { # 比如你的查询比较复杂,涉及到很多的子查询,那你可以考虑通过bool查询包裹这些子查询 # 每一个子查询都会计算出这个doc针对于它这种查询得到的相关性得分。 # 最终由bool查询将这些得分合并为一个最终的得分 "bool": { # 必须匹配到XXX, 并且会得出相关性得分 # address中必须包含mill "must": [ { "match": { "address": "mill" } }, ], # 在满足must的基础上,should条件不满足也可以,但是如果也匹配上了,相关性得分会增加 # 如果没有must的话,should中的条件必须满足一个 "should": [ { "match": { "address": "lane" } } ], "must_not": [ # 一定不包含谁 { "match": { "address": "mill" } }, ], # filter中的表达式仅仅对数据进行过滤,但是不会影响搜索结果的相关度得分。 # 所以你如果不希望添加的过滤条件影响最终的doc排序的话,可以将条件放在filter中。 # query是会计算doc的相关度得分的,得分越高,越靠前。 "filter": { "range": { # 按照范围过滤 "balance": { # 指定过滤的字段 "gte": 20000s # 高于20000 "lte": 30000 # 低于30000 } } } } }
默认的排序规则是按照_score降序排序,但像上面说的那样,如果全部都是filter的话它就不会计算得分,也就是说所有的得分全是1,这时候就需要定制排序规则,定义的语法我在上面写了
28、查询名称中包含“白日梦”的doc,并且按照star排序
高亮、排序、分页以及_source
指定需要的字段都可以进一步作用在query
的结果上。
# ES默认的排序规则是按照 _score 字段降序排序的 # 但是ES允许你像下面这样定制排序规则 GET /your_index/your_type/_search { "query": { "match": {"name":"白日 梦"} }, # 指定排序条件 "sort":[ # 指定排序字段为 star {"star":"desc"} ] }
29、分页查询
如:从第一条doc开启查,查10条。(如果你不使用from、to搜索的话,默认就搜索前10条)
GET /your_index/your_type/_search { "query": { "match_all": {} }, "from": 0, # 0:是第一个doc "size": 10 } # 还可以像这样发起分页请求 GET /your_index/your_type/_search?size=10 GET /your_index/your_type/_search?size=10&from=20 # deep paging 问题 比如系统中只有3个primary shard,1个replica shard,共有6W条数据。 用户希望查询第1000页,每页10条数据。也就是1000*10 = 10001 ~ 10010 条数据 假如说用户将这个分页请求会打向ES集群中的replica shard,接下来会发生什么? 回答: 接收到请求的shard 我们称它为coordinate node(协调节点),它会将请求转发到三个primary, 每个primary shard都会取出它们的第1~10010条数据id,返回给coordinate node, 也就是说coordinate node总共会接收到30030个id,然后coordinate node再拿着这些id发起mget请求获取数据 对获取到的结果30030排序处理,最后取相关性得分最高的10条返回给用户。 所以当分页过深的时候是非常消耗内存、网络带宽、CPU的。
30、指定要查询出来的doc的某几个字段。如下:
# 假设白日梦对应的json长下面这样: { "name":"白日梦", “address”:"beijing", "gender":"man" } # 然后我只想检索出name字段,其他的不想知道,可以像下面这样通过_sorce限制 GET /your_index/your_type/_search { "query": { "match_all": {} }, # ES会返回全文JSON,通过_source可以指定返回的字段 "_source": ["name"], }
31、filter过滤,查询name中包含白日梦,且star大于100的doc。
GET /your_index/your_type/_search { "query": { # 可以使用bool封装包括多个查询条件 “bool":{ "must":{"match": {"name":"白日 梦"}} # 指定按照star的范围进行filter "filter":{ # range既能放在query中,也能放在filter中。 # 如果放在filter中,range过滤的动作不会影响最终的得分。 # 但是放在query中,range动作会影响最终的得分。 "range":{ “star”:{"gt":100} } } } } } # 拓展: # 关于range还可以像这样过滤时间 "range":{ # 指定birthday范围为最近一个月的doc "birthday":{ "gt":"2021-01-20||-30d" } } # 或者使用now语法 # 指定birthday范围为最近一个月的doc "birthday":{ "gt":"now-30d" } }
32、指定对返回的doc中指定字段中的指定单词高亮显示。
GET /your_index/your_type/_search { "query": { "match": {"name":"白日 梦"} }, "highlight":{ # 高亮显示 "fields":{ # 指定高亮的字段为 firstname "firstname":{} } } # 最终得到的返回值类似下面这样 ... "hits" : { "total" : 1000,# 1000个 "max_score" : null, "hits" : [ { "_index" : "bank", "_type" : "_doc", "_id" : "0", "sort": [0], "_score" : 0.777777, "_source" : {"account_number":0, "balance":16623, "firstname":"我是白", "lastname":"日梦", "state":"CO"} }], "highlight":{ "firstname":[ "我是<em>白</em>" ] } ...
参考:https://www.elastic.co/guide/en/elasticsearch/reference/6.2/query-dsl.html