当前位置 博文首页 > 赐我白日梦:白日梦的Elasticsearch实战笔记,32个查询案例、15

    赐我白日梦:白日梦的Elasticsearch实战笔记,32个查询案例、15

    作者:赐我白日梦 时间:2021-01-30 15:21

    目录
    • 一、导读
    • 二、福利:账号借用
    • 三、_search api 搜索api
      • 3.1、什么是query string search?
      • 3.2、什么是query dsl?
      • 3.3、干货!32个查询案例!
    • 四、聚合分析
      • 4.1、什么是聚合分析?
      • 4.2、干货!15个聚合分析案例
    • 五、7个查询优化技巧
    • 推荐阅读



    一、导读

    Hi!大家久等了!时隔10天,白日梦的Elasticsearch笔记进阶篇终于甘完了!本次更新依然是干货满满!

    下面会和大家分享 32种查询方法、15中聚合方式、7种优化后的查询技巧。欢迎大家转发支持!

    如果对ES中的各种概念不太清楚可以去看上一篇文章,白日梦的ES笔记-基础篇,并且有些概念不理解并不会影响你看懂本文中为大家介绍的各种查询方式。

    下一篇(白日梦的ES系列笔记第三篇)文章会跟大家一起杀回到基础部分,系统的做一次概念上的扫盲!

    最后一篇(ES系列笔记第四篇)以编程语言实战为主,不出意外的话会以视频的方式和大家见面。

    文章公众号首发! 欢迎关注白日梦!第一时间追更新!

    点击链接阅读原文:json的格式会好看很多!



    二、福利:账号借用

    好消息!!!如果你嫌安装ES麻烦,想使用现成的ES学习,可以免费白嫖白日梦的搭建在公网上的ES实例(有效期还有340多天,预计到2022年初才过期哦)。关注此公号后台回复:白嫖 可得到账号密码。

    Notice!!!我不能保证它一定是安全可用哦,毕竟IP直接暴露在公网上是极有可能被黑的。如果你发现服务不可用,可以跟我说一下。我提前做好了镜像,可快速将系统回复如初。(为了安全,我也会不定期更新IP、账号密码)所以大家拿它用来学习还行,不要往上面放重要的数据哈!

    关注白日梦后台回复:白嫖 ,即可领取账号密码。

    关注白日梦后台回复:白嫖 ,即可领取账号密码。

    关注白日梦后台回复:白嫖 ,即可领取账号密码。

    点击链接阅读原文:可以找到我公众号的二维码。


    另外我也推荐大家阅读原文,json的格式会好看很多!



    三、_search api 搜索api

    search api也是我们最需要了解和掌握的APi。因为绝大部分时间你使用ES就是为了检索嘛,所以下面一起看一下ES有哪些检索API,当然最终的目的是大家有拥有选择出一种适合自己业务的检索方式的能力。

    我又来吹牛了!

    如果你不学白日梦跟你介绍的这些查询方式、技巧。我敢说你八成不懂别人用Java或者Golang写出来的代码。

    相反如果你看懂了下面的几十个Case后,我敢说你自己可以分分钟独立的用熟悉的编程语言写出对应的查询代码!

    3.1、什么是query string search?

    所谓的query string search其实就是ES为我们提供的一种检索方式。下面这行请求就是典型的通过 query string search的方式进行检索。

    其实这种检索方式很少用。直观上看 query string search 这种检索方式的特点就是它的请求参数全部写在URI中。

    GET /your_index/your_type/_search?q=*&sort=account_number:asc&pretty
    

    解读一下上面的 query string search: q=* ,表示匹配index=bank的下的所有doc,sort=account_number:asc表示告诉ES,结果按照account_number字段升序排序,pretty是告诉ES,返回一个漂亮的json格式的数据。

    上面的q还可以写成下面这样:

    GET /your_index/your_type/_search?q=自定义field:期望的值
    GET /your_index/your_type/_search?q=+自定义field:期望的值
    GET /your_index/your_type/_search?q=-自定义field:期望的值
    

    解读ES返回的响应如下(包括后面的query dsl的几十种查询案例的返回值也长这样,并且下面不再重复分析这个返回值都有啥字段了,所以推荐你好好看下这个返回值再去浏览本文的重头戏:query dsl 和 查询优化技巧哈):

    {
      "took" : 63,// 耗费的时间
      // 是否超时了,默认情况下不存在time_out,比如你的搜索耗时1分钟,它就等1分钟,但是不超时
      // 在发送搜索请求时可以指定超时时间
      // 比如你指定了10ms超时,它就会把这10ms内获得的数据返回给你
      "timed_out" : false,
      "_shards" : { // 你的搜索请求打到了几个shard上面去。
        // Primary Shard可以承接读、写流量。Replica Shard会承接读流量。
        // 因为我是默认配置,有五个primary shard。
        // 所以它的搜索请求会被打到5个分片上去,并且都成功了
        "total" : 5,
        "successful" : 5,
        "skipped" : 0,// 跳过了0个
        "failed" : 0 // 失败了0个
      },
      "hits" : {//命中的情况
        "total" : 1000,// 命中率 1000个
        // _score 全文检索时使用,这个相关性得分越高,说明doc和检索的内容的越相关、越匹配
        // max_score就是最大的 _score
        "max_score" : null,
        // 默认查询前10条,直接返回每个doc的完整数据
        "hits" : [ {   
          "_index" : "bank",// 索引
          "_type" : "_doc",// type
          "_id" : "0",// id 
          "sort": [0],
          "_score" : null,// 相关性得分
          // _source里面存放的是doc的具体数据
          "_source" : 		{"account_number":0,
                           "balance":16623,
                           "firstname":"Bradshaw",
                           "lastname":"Mckenzie",
                           "age":29,
                           "gender":"F",
                           "address":"244 Columbus Place",
                           "employer":"Euron",
                           "email":"bradshawmckenzie@euron.com",
                           "city":"Hobucken",
                           "state":"CO"}
        		},
    		 {
          "_index" : "bank",
          "_type" : "_doc",
          "_id" : "1",
          "sort": [1],
          "_score" : null,
          "_source" : {"account_number":1,
                       "balance":39225,
                       "firstname":"Amber",
                       "lastname":"Duke",
                       "age":32,
                       "gender":"M",
                       "address":"880 Holmes Lane",
                       "employer":"Pyrami",
                       "email":"amberduke@pyrami.com",
                       "city":"Brogan",
                       "state":"IL"}
        }, ...
        ]
      }
    }
    

    指定超时时间: GET /_search?timeout=10ms 在进行优化时,可以考虑使用timeout, 比如: 正常来说我们可以在10s内获取2000条数据,但是指定了timeout,发生超时后我们可以获取10ms中获取到的 100条数据。



    3.2、什么是query dsl?

    dsl 全程 domain specified language

    不论是query string search 还是这小节的query specified language它们本质上都是在发送Resutful类型的网络请求。相对于 query string search 的将所有的请求参数都写在URI中,query dsl 一般长下面这样:

    GET /yourIndex/yourType/_search
    {
      // 很多请求参数
    }
    

    说的直白一点,query string search 更像是http中的GET请求,因为它没有请求体。而本小节的query dsl 更像是http 中的POST请求。



    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



    四、聚合分析



    4.1、什么是聚合分析?

    聚合分析有点类似于SQL语句中的那种group by、where age > 20 and age < 30、这种操作。常见的聚合分析就是根据某一个字段进行分组分析,要求这个字段是不能被分词的,如果被聚合的字段被分词,按照倒排索引的方式去索引的话,就不得不去扫描整个倒排索引(才可能将被聚合的字段找全,效率很低)。

    聚合分析是基于doc value的数据结果集进行操作的,这个doc value 其实就是正排索引(现在了解就好,下一篇文章统一扫盲),

    关于聚合分析有三个重要的概念:

    • bucket