zl程序教程

您现在的位置是:首页 >  工具

当前栏目

白日梦的Elasticsearch实战笔记,ES账号免费借用、32个查询案例、15个聚合案例、7个查询优化技巧。(二)

ES案例elasticsearch笔记 优化 查询 实战 技巧
2023-09-27 14:25:58 时间
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


Elasticsearch全观测技术解析与应用(构建日志、指标、APM统一观测平台) 立即下载