• ElasticSearch总结2-高级搜索


    参考资料:Elasticsearch: 权威指南 

    在线地址:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html

    注:我用的ElasticSearch版本是6.5 使用的命令部分会和书中的不一样

    1. 结构化搜索

    1.1 精确值查找

      当进行精确值查找时, 我们会使用过滤器(filters)。过滤器很重要,因为它们执行速度非常快,不会计算相关度(直接跳过了整个评分阶段)而且很容易被缓存。请尽可能多的使用过滤式查询。

      term 查询数字

      我们首先来看最为常用的 term 查询, 可以用它处理数字(numbers)、布尔值(Booleans)、日期(dates)以及文本(text)。

      让我们以下面的例子开始介绍,创建并索引一些表示产品的文档,文档里有字段 `price` 和 `productID` ( `价格` 和 `产品ID` )

    POST /my_store/products/_bulk
    { "index": { "_id": 1 }}
    { "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
    { "index": { "_id": 2 }}
    { "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
    { "index": { "_id": 3 }}
    { "price" : 30, "productID" : "JODL-X-1937-#pV7" }
    { "index": { "_id": 4 }}
    { "price" : 30, "productID" : "QQPX-R-3956-#aD8" }

      我们想要做的是查找具有某个价格的所有产品,有关系数据库背景的人肯定熟悉 SQL,如果我们将其用 SQL 形式表达,会是下面这样:

    SELECT document FROM products WHERE  price = 20

      在 Elasticsearch 的查询表达式(query DSL)中,我们可以使用 term 查询达到相同的目的。 term 查询会查找我们指定的精确值。作为其本身, term 查询是简单的。它接受一个字段名以及我们希望查找的数值:

    GET /my_store/products/_search
    {
      "query" : {
        "term" : {
          "price" : 20
        }
      }
    }

      通常当查找一个精确值的时候,我们不希望对查询进行评分计算。只希望对文档进行包括或排除的计算,所以我们会使用 constant_score 查询以非评分模式来执行 term 查询并以一作为统一评分。

      最终组合的结果是一个 constant_score 查询,它包含一个 term 查询:

    GET /my_store/products/_search
    {
        "query" : {
            "constant_score" : { 
                "filter" : {
                    "term" : { 
                        "price" : 20
                    }
                }
            }
        }
    }

      term 查询文本

      如本部分开始处提到过的一样 ,使用 term 查询匹配字符串和匹配数字一样容易。如果我们想要查询某个具体 productID 的产品,使用 SQL 表达式会是如下这样:

    SELECT product FROM products WHERE  productID = "XHDK-A-1293-#fJ3"

      转换成查询表达式(query DSL),同样使用 term 查询,形式如下:

    GET /my_store/products/_search
    {
        "query" : {
            "constant_score" : {
                "filter" : {
                    "term" : {
                        "productID" : "XHDK-A-1293-#fJ3"
                    }
                }
            }
        }
    }

      但这里有个小问题:我们无法获得期望的结果。为什么呢?问题不在 term 查询,而在于索引数据的方式。 如果我们使用 analyze API (分析 API),我们可以看到这里的 productID 被拆分成多个更小的 token :

    GET /my_store/_analyze
    {
      "field": "productID",
      "text": "XHDK-A-1293-#fJ3"
    }

      分析结果:

    {
      "tokens" : [ {
        "token" :        "xhdk",
        "start_offset" : 0,
        "end_offset" :   4,
        "type" :         "<ALPHANUM>",
        "position" :     1
      }, {
        "token" :        "a",
        "start_offset" : 5,
        "end_offset" :   6,
        "type" :         "<ALPHANUM>",
        "position" :     2
      }, {
        "token" :        "1293",
        "start_offset" : 7,
        "end_offset" :   11,
        "type" :         "<NUM>",
        "position" :     3
      }, {
        "token" :        "fj3",
        "start_offset" : 13,
        "end_offset" :   16,
        "type" :         "<ALPHANUM>",
        "position" :     4
      } ]
    }

      这里有几点需要注意: Elasticsearch 用 4 个不同的 token,而不是单个 token 来表示这个 UPC 。 所有字母都是小写的。 丢失了连字符和哈希符( # )。

      所以当我们用 term 查询查找精确值 XHDK-A-1293-#fJ3 的时候,找不到任何文档,因为它并不在我们的倒排索引中,正如前面呈现出的分析结果,索引里有四个 token 。

      显然这种对 ID 码或其他任何精确值的处理方式并不是我们想要的。所以我们不能用默认mapping了,我们需要重新定义mapping,指定productID是一个精确值。

    DELETE /my_store
    
    PUT /my_store 
    {
        "mappings" : {
            "products" : {
                "properties" : {
                    "productID" : {
                        "type" : "keyword",
                        "index" : true 
                    }
                }
            }
        }
    
    }

      删除索引是必须的,因为我们不能更新已存在的映射。

      在索引被删除后,我们可以创建新的索引并为其指定自定义映射。

      这里我们告诉 Elasticsearch ,我们不想对 productID 做任何分析。

         我们再初始化上面的订单数据,并执行productID查询,发现可以看到结果了。

    1.2 组合过滤器

       前面的两个例子都是单个过滤器(filter)的使用方式。 在实际应用中,我们很有可能会过滤多个值或字段。比方说,怎样用 Elasticsearch 来表达下面的 SQL ?

    SELECT product FROM products WHERE (price = 20 OR productID = "XHDK-A-1293-#fJ3") AND  (price != 30)

      这种情况下,我们需要 bool (布尔)过滤器。 这是个 复合过滤器(compound filter) ,它可以接受多个其他过滤器作为参数,并将这些过滤器结合成各式各样的布尔(逻辑)组合。

      布尔过滤器

      一个 bool 过滤器由三部分组成:

    {
       "bool" : {
          "must" :     [],
          "should" :   [],
          "must_not" : [],
       }
    }

      must 所有的语句都 必须(must) 匹配,与 AND 等价。

      must_not 所有的语句都 不能(must not) 匹配,与 NOT 等价。

      should 至少有一个语句要匹配,与 OR 等价。

      就这么简单! 当我们需要多个过滤器时,只须将它们置入 bool 过滤器的不同部分即可。

      一个 bool 过滤器的每个部分都是可选的(例如,我们可以只有一个 must 语句),而且每个部分内部可以只有一个或一组过滤器。

      用 Elasticsearch 来表示本部分开始处的 SQL 例子,将两个 term 过滤器置入 bool 过滤器的 should 语句内,再增加一个语句处理 NOT 非的条件:

    GET /my_store/products/_search
    {
       "query" : {
          "bool" : {
            "should" : [
                { "term" : {"price" : 20}}, 
                { "term" : {"productID" : "XHDK-A-1293-#fJ3"}} 
            ],
            "must_not" : {
                "term" : {"price" : 30} 
            }
          }
       }
    }

       

      嵌套布尔过滤器

      尽管 bool 是一个复合的过滤器,可以接受多个子过滤器,需要注意的是 bool 过滤器本身仍然还只是一个过滤器。 这意味着我们可以将一个 bool 过滤器置于其他 bool 过滤器内部,这为我们提供了对任意复杂布尔逻辑进行处理的能力。 对于以下这个 SQL 语句:

    SELECT document FROM products WHERE productID = "KDKE-B-9947-#kL5" OR (productID = "JODL-X-1937-#pV7" AND price = 30 )

      我们将其转换成一组嵌套的 bool 过滤器:

    GET /my_store/products/_search
    {
       "query" : {
          "bool" : {
            "should" : [
              { "term" : {"productID" : "KDKE-B-9947-#kL5"}},
              { "bool" : { 
                "must" : [
                    { "term" : {"productID" : "JODL-X-1937-#pV7"}}, 
                    { "term" : {"price" : 30}} 
                ]
              }}
            ]
          }
       }
    }

      *因为 term 和 bool 过滤器是兄弟关系,他们都处于外层的布尔逻辑 should 的内部,返回的命中文档至少须匹配其中一个过滤器的条件。

      *这两个 term 语句作为兄弟关系,同时处于 must 语句之中,所以返回的命中文档要必须都能同时匹配这两个条件。

    1.3 查找多个精确值

      term 查询对于查找单个值非常有用,但通常我们可能想搜索多个值。 如果我们想要查找价格字段值为 $20 或 $30 的文档该如何处理呢?不需要使用多个 term 查询,我们只要用单个 terms 查询(注意末尾的 s ),terms 查询好比是 term 查询的复数形式(以英语名词的单复数做比)。它几乎与 term 的使用方式一模一样,与指定单个价格不同,我们只要将 term 字段的值改为数组即可:

      与 term 查询一样,也需要将其置入 filter 语句的常量评分查询中使用:

    GET /my_store/products/_search
    {
        "query" : {
            "constant_score" : {
                "filter" : {
                    "terms" : { 
                        "price" : [20, 30]
                    }
                }
            }
        }
    }

      包含,而不是相等

      一定要了解 term 和 terms 是 包含(contains) 操作,而非 等值(equals)判断。 如何理解这句话呢? 如果我们有一个 term(词项)过滤器 { "term" : { "tags" : "search" } } ,它会与以下两个文档 同时 匹配:

    { "tags" : ["search"] }
    { "tags" : ["search", "open_source"] } 

      尽管第二个文档包含除 search 以外的其他词,它还是被匹配并作为结果返回。

    1.4 范围查询

      数字范围

      对于数字,只介绍如何处理精确值查询。实际上,对数字范围进行过滤有时会更有用。例如,我们可能想要查找所有价格大于 $20 且小于 $40 美元的产品。 在 SQL 中,范围查询可以表示为:

    SELECT document FROM products WHERE price BETWEEN 20 AND 40

      Elasticsearch 有 range 查询,不出所料地,可以用它来查找处于某个范围内的文档。range 查询可同时提供包含(inclusive)和不包含(exclusive)这两种范围表达式,可供组合的选项如下:

        gt: > 大于(greater than)

        lt: < 小于(less than)

        gte: >= 大于或等于(greater than or equal to)

        lte: <= 小于或等于(less than or equal to)

    GET /my_store/products/_search
    {
        "query" : {
            "constant_score" : {
                "filter" : {
                    "range" : {
                        "price" : {
                            "gte" : 20,
                            "lt"  : 40
                        }
                    }
                }
            }
        }
    }

      日期范围

      range 查询同样可以应用在日期字段上:

    "range" : {
        "timestamp" : {
            "gt" : "2014-01-01 00:00:00",
            "lt" : "2014-01-07 00:00:00"
        }
    }

      当使用它处理日期字段时, range 查询支持对 日期计算(date math) 进行操作,比方说,如果我们想查找时间戳在过去一小时内的所有文档:

    "range" : {
        "timestamp" : {
            "gt" : "now-1h"
        }
    }

      这个过滤器会一直查找时间戳在过去一个小时内的所有文档,让过滤器作为一个时间 滑动窗口(sliding window) 来过滤文档。 日期计算还可以被应用到某个具体的时间,并非只能是一个像 now 这样的占位符。

      只要在某个日期后加上一个双管符号 (||) 并紧跟一个日期数学表达式就能做到

    "range" : {
        "timestamp" : {
            "gt" : "2014-01-01 00:00:00",
            "lt" : "2014-01-01 00:00:00||+1M" 
        }
    }

      早于 2014 年 1 月 1 日加 1 月(2014 年 2 月 1 日 零时)

      字符串范围

      range 查询同样可以处理字符串字段,字符串范围可采用 字典顺序(lexicographically) 或字母顺序(alphabetically)。例如,下面这些字符串是采用字典序(lexicographically)排序的: 5, 50, 6, B, C, a, ab, abb, abc, b

      在倒排索引中的词项就是采取字典顺序(lexicographically)排列的,这也是字符串范围可以使用这个顺序来确定的原因。

      如果我们想查找从 a 到 b (不包含)的字符串,同样可以使用 range 查询语法:

    "range" : {
        "title" : {
            "gte" : "a",
            "lt" :  "b"
        }
    }

      注意基数:

      数字和日期字段的索引方式使高效地范围计算成为可能。但字符串却并非如此,要想对其使用范围过滤,Elasticsearch 实际上是在为范围内的每个词项都执行 term 过滤器,这会比日期或数字的范围过滤慢许多。

      字符串范围在过滤 低基数(low cardinality) 字段(即只有少量唯一词项)时可以正常工作,但是唯一词项越多,字符串范围的计算会越慢。

    1.5  处理Null值

      如果一个字段没有值,那么如何将它存入倒排索引中的呢? 这是个有欺骗性的问题,因为答案是:什么都不存。

      如何将某个不存在的字段存储在这个数据结构中呢?无法做到!简单的说,一个倒排索引只是一个 token 列表和与之相关的文档信息,如果字段不存在,那么它也不会持有任何 token,也就无法在倒排索引结构中表现。

      最终,这也就意味着,null, [] (空数组)和 [null] 所有这些都是等价的,它们无法存于倒排索引中。显然,世界并不简单,数据往往会有缺失字段,或有显式的空值或空数组。为了应对这些状况,Elasticsearch 提供了一些工具来处理空或缺失值。

      存在查询

      第一件武器就是 exists 存在查询。这个查询会返回那些在指定字段有任何值的文档,让我们索引一些示例文档并用标签的例子来说明:

      我们的目标是找到那些被设置过标签字段的文档,并不关心标签的具体内容。只要它存在于文档中即可,用 SQL 的话就是用 IS NOT NULL 非空进行查询:

    SELECT tags FROM posts WHERE tags IS NOT NULL

      在 Elasticsearch 中,使用 exists 查询的方式如下:

    GET /my_index/posts/_search
    {
        "query" : {
            "constant_score" : {
                "filter" : {
                    "exists" : { "field" : "tags" }
                }
            }
        }
    }

      尽管文档有 null 值,但它仍会被命中返回。字段之所以存在,是因为标签有实际值( search )可以被索引,所以 null 对过滤不会产生任何影响。 显而易见,只要 tags 字段存在项(term)的文档都会命中并作为结果返回。

      缺失查询

      早版本的missing关键字专门用来判断缺少字段,但后来发现exists可以同时满足为空、非空这2个需求,与以下 SQL 表达的意思类似:

    SELECT tags FROM posts WHERE tags IS NULL

      ElasticSearch的使用方式:

    GET /my_index/posts/_search
    {
        "query" : {
            "bool": {
                "must_not": {
                    "exists": {
                        "field" : "tags"
                    }
                }
            }
        }
    }

      当 null 的意思是 null

      有时候我们需要区分一个字段是没有值,还是它已被显式的设置成了 null 。

      在之前例子中,我们看到的默认的行为是无法做到这点的;数据被丢失了。不过幸运的是,我们可以选择将显式的 null 值替换成我们指定 占位符(placeholder) 。 在为字符串(string)、数字(numeric)、布尔值(Boolean)或日期(date)字段指定映射时,同样可以为之设置 null_value 空值,用以处理显式 null 值的情况。不过即使如此,还是会将一个没有值的字段从倒排索引中排除。

      当选择合适的 null_value 空值的时候,需要保证以下几点: 它会匹配字段的类型,我们不能为一个 date 日期字段设置字符串类型的 null_value 。 它必须与普通值不一样,这可以避免把实际值当成 null 空的情况。

    2. 全文搜索

    2.1 匹配查询

      匹配查询 match 是个 核心 查询。无论需要查询什么字段, match 查询都应该会是首选的查询方式。它是一个高级 全文查询 ,这表示它既能处理全文字段,又能处理精确字段。 这就是说, match 查询主要的应用场景就是进行全文搜索,我们以下面一个简单例子来说明全文搜索是如何工作的:

      我们先创建一个搜索,并添加数据

    DELETE /my_index 
    
    PUT /my_index
    { "settings": { "number_of_shards": 1 }} 
    
    POST /my_index/my_type/_bulk
    { "index": { "_id": 1 }}
    { "title": "The quick brown fox" }
    { "index": { "_id": 2 }}
    { "title": "The quick brown fox jumps over the lazy dog" }
    { "index": { "_id": 3 }}
    { "title": "The quick brown fox jumps over the quick dog" }
    { "index": { "_id": 4 }}
    { "title": "Brown fox brown dog" }

    2.2 单个词查询

      我们用第一个示例来解释使用 match 查询搜索全文字段中的单个词:

    GET /my_index/my_type/_search
    {
        "query": {
            "match": {
                "title": "QUICK!"
            }
        }
    }

      Elasticsearch 执行上面这个 match 查询的步骤是:

      检查字段类型 。 标题 title 字段是一个 string 类型( analyzed )已分析的全文字段,这意味着查询字符串本身也应该被分析。

      分析查询字符串 。 将查询的字符串 QUICK! 传入标准分析器中,输出的结果是单个项 quick 。因为只有一个单词项,所以 match 查询执行的是单个底层 term 查询。

      查找匹配文档 。 用 term 查询在倒排索引中查找 quick 然后获取一组包含该项的文档,本例的结果是文档:1、2 和 3 。

      为每个文档评分 。用 term 查询计算每个文档相关度评分 _score ,这是种将词频(term frequency,即词 quick 在相关文档的 title 字段中出现的频率)和反向文档频率(inverse document frequency,即词 quick 在所有文档的 title 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。参见 相关性的介绍 。

      这个过程给我们以下(经缩减)结果:

    "hits": [
     {
        "_id":      "1",
        "_score":   0.5, 
        "_source": {
           "title": "The quick brown fox"
        }
     },
     {
        "_id":      "3",
        "_score":   0.44194174, 
        "_source": {
           "title": "The quick brown fox jumps over the quick dog"
        }
     },
     {
        "_id":      "2",
        "_score":   0.3125, 
        "_source": {
           "title": "The quick brown fox jumps over the lazy dog"
        }
     }
    ]

      * 文档 1 最相关,因为它的 title 字段更短,即 quick 占据内容的一大部分。

      * 文档 3 比 文档 2 更具相关性,因为在文档 3 中 quick 出现了两次。

    2.3 多词查询

      如果我们一次只能搜索一个词,那么全文搜索就会不太灵活,幸运的是 match 查询让多词查询变得简单:

    GET /my_index/my_type/_search
    {
        "query": {
            "match": {
                "title": "BROWN DOG!"
            }
        }
    }

      上面这个查询返回所有四个文档:

    {
      "hits": [
         {
            "_id":      "4",
            "_score":   0.73185337, 
            "_source": {
               "title": "Brown fox brown dog"
            }
         },
         {
            "_id":      "2",
            "_score":   0.47486103, 
            "_source": {
               "title": "The quick brown fox jumps over the lazy dog"
            }
         },
         {
            "_id":      "3",
            "_score":   0.47486103, 
            "_source": {
               "title": "The quick brown fox jumps over the quick dog"
            }
         },
         {
            "_id":      "1",
            "_score":   0.11914785, 
            "_source": {
               "title": "The quick brown fox"
            }
         }
      ]
    }

      * 文档 4 最相关,因为它包含词 "brown" 两次以及 "dog" 一次。

      * 文档 2、3 同时包含 brown 和 dog 各一次,而且它们 title 字段的长度相同,所以具有相同的评分。

      * 文档 1 也能匹配,尽管它只有 brown 没有 dog 。

      因为 match 查询必须查找两个词( ["brown","dog"] ),它在内部实际上先执行两次 term 查询,然后将两次查询的结果合并作为最终结果输出。为了做到这点,它将两个 term 查询包入一个 bool 查询中,详细信息见 布尔查询。 以上示例告诉我们一个重要信息:即任何文档只要 title 字段里包含 指定词项中的至少一个词 就能匹配,被匹配的词项越多,文档就越相关。

      提高精度

      用 任意 查询词项匹配文档可能会导致结果中出现不相关的长尾。这是种散弹式搜索。可能我们只想搜索包含 所有 词项的文档,也就是说,不去匹配 brown OR dog ,而通过匹配 brown AND dog 找到所有文档。 match 查询还可以接受 operator 操作符作为输入参数,默认情况下该操作符是 or 。我们可以将它修改成 and 让所有指定词项都必须匹配:

    GET /my_index/my_type/_search
    {
        "query": {
            "match": {
                "title": {      
                    "query":    "BROWN DOG!",
                    "operator": "and"
                }
            }
        }
    }

      match 查询的结构需要做稍许调整才能使用 operator 操作符参数。

      这个查询可以把文档 1 排除在外,因为它只包含两个词项中的一个。

      控制精度

      在所有与任意间二选一有点过于非黑即白。如果用户给定 5 个查询词项,想查找只包含其中 4 个的文档,该如何处理?将 operator 操作符参数设置成 and 只会将此文档排除。

      有时候这正是我们期望的,但在全文搜索的大多数应用场景下,我们既想包含那些可能相关的文档,同时又排除那些不太相关的。换句话说,我们想要处于中间某种结果。

      match 查询支持 minimum_should_match 最小匹配参数,这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数,因为我们无法控制用户搜索时输入的单词数量:

    GET /my_index/my_type/_search
    {
      "query": {
        "match": {
          "title": {
            "query":                "quick brown dog",
            "minimum_should_match": "75%"
          }
        }
      }
    }

      当给定百分比的时候, minimum_should_match 会做合适的事情:在之前三词项的示例中, 75% 会自动被截断成 66.6% ,即三个里面两个词。无论这个值设置成什么,至少包含一个词项的文档才会被认为是匹配的。

      参数 minimum_should_match 的设置非常灵活,可以根据用户输入词项的数目应用不同的规则。完整的信息参考文档 https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-minimum-should-match.html#query-dsl-minimum-should-match

      为了完全理解 match 是如何处理多词查询的,我们就需要查看如何使用 bool 查询将多个查询条件组合在一起。

    2.4 组合查询

      在组合过滤器 中,我们讨论过如何使用 bool 过滤器通过 and 、 or 和 not 逻辑组合将多个过滤器进行组合。

      在查询中, bool 查询有类似的功能,只有一个重要的区别。 过滤器做二元判断:文档是否应该出现在结果中?但查询更精妙,它除了决定一个文档是否应该被包括在结果中,还会计算文档的 相关程度 。

      与过滤器一样, bool 查询也可以接受 must 、 must_not 和 should 参数下的多个查询语句。比如:

    GET /my_index/my_type/_search
    {
      "query": {
        "bool": {
          "must":     { "match": { "title": "quick" }},
          "must_not": { "match": { "title": "lazy"  }},
          "should": [
                      { "match": { "title": "brown" }},
                      { "match": { "title": "dog"   }}
          ]
        }
      }
    }

      以上的查询结果返回 title 字段包含词项 quick 但不包含 lazy 的任意文档。目前为止,这与 bool 过滤器的工作方式非常相似。

      区别就在于两个 should 语句,也就是说:一个文档不必包含 brown 或 dog 这两个词项,但如果一旦包含,我们就认为它们 更相关 :

    {
      "hits": [
         {
            "_id":      "3",
            "_score":   0.70134366, 
            "_source": {
               "title": "The quick brown fox jumps over the quick dog"
            }
         },
         {
            "_id":      "1",
            "_score":   0.3312608,
            "_source": {
               "title": "The quick brown fox"
            }
         }
      ]
    }

      * 文档 3 会比文档 1 有更高评分是因为它同时包含 brown 和 dog 。

      评分计算

      bool 查询会为每个文档计算相关度评分 _score ,再将所有匹配的 must 和 should 语句的分数 _score 求和,最后除以 must 和 should 语句的总数。 must_not 语句不会影响评分;它的作用只是将不相关的文档排除。

      控制精度

      所有 must 语句必须匹配,所有 must_not 语句都必须不匹配,但有多少 should 语句应该匹配呢?默认情况下,没有 should 语句是必须匹配的,只有一个例外:那就是当没有 must 语句的时候,至少有一个 should 语句必须匹配。

      就像我们能控制 match 查询的精度 一样,我们可以通过 minimum_should_match 参数控制需要匹配的 should 语句的数量,它既可以是一个绝对的数字,又可以是个百分比:

    GET /my_index/my_type/_search
    {
      "query": {
        "bool": {
          "should": [
            { "match": { "title": "brown" }},
            { "match": { "title": "fox"   }},
            { "match": { "title": "dog"   }}
          ],
          "minimum_should_match": 2 
        }
      }
    }

      * 这也可以用百分比表示。

      这个查询结果会将所有满足以下条件的文档返回: title 字段包含 "brown" AND "fox" 、 "brown" AND "dog" 或 "fox" AND "dog" 。如果有文档包含所有三个条件,它会比只包含两个的文档更相关。

    2.5 如何使用布尔匹配

      目前为止,可能已经意识到多词 match 查询只是简单地将生成的 term 查询包裹在一个 bool 查询中。如果使用默认的 or 操作符,每个 term 查询都被当作 should 语句,这样就要求必须至少匹配一条语句。以下两个查询是等价的:

    {
        "match": { "title": "brown fox"}
    }
    {
      "bool": {
        "should": [
          { "term": { "title": "brown" }},
          { "term": { "title": "fox"   }}
        ]
      }
    }

      如果使用 and 操作符,所有的 term 查询都被当作 must 语句,所以 所有(all) 语句都必须匹配。以下两个查询是等价的:

    {
        "match": {
            "title": {
                "query":    "brown fox",
                "operator": "and"
            }
        }
    }
    {
      "bool": {
        "must": [
          { "term": { "title": "brown" }},
          { "term": { "title": "fox"   }}
        ]
      }
    }

      如果指定参数 minimum_should_match ,它可以通过 bool 查询直接传递,使以下两个查询等价:

    {
        "match": {
            "title": {
                "query":                "quick brown fox",
                "minimum_should_match": "75%"
            }
        }
    }
    {
      "bool": {
        "should": [
          { "term": { "title": "brown" }},
          { "term": { "title": "fox"   }},
          { "term": { "title": "quick" }}
        ],
        "minimum_should_match": 2 
      }
    }

      * 因为只有三条语句,match 查询的参数 minimum_should_match 值 75% 会被截断成 2 。即三条 should 语句中至少有两条必须匹配。

      当然,我们通常将这些查询用 match 查询来表示,但是如果了解 match 内部的工作原理,我们就能根据自己的需要来控制查询过程。有些时候单个 match 查询无法满足需求,比如为某些查询条件分配更高的权重。我们会在下一小节中看到这个例子。

    2.6 查询语句提升权重

      当然 bool 查询不仅限于组合简单的单个词 match 查询,它可以组合任意其他的查询,以及其他 bool 查询。普遍的用法是通过汇总多个独立查询的分数,从而达到为每个文档微调其相关度评分 _score 的目的。

      假设想要查询关于 “full-text search(全文搜索)” 的文档,但我们希望为提及 “Elasticsearch” 或 “Lucene” 的文档给予更高的 权重 ,这里 更高权重 是指如果文档中出现 “Elasticsearch” 或 “Lucene” ,它们会比没有的出现这些词的文档获得更高的相关度评分 _score ,也就是说,它们会出现在结果集的更上面。

      一个简单的 bool 查询 允许我们写出如下这种非常复杂的逻辑:

    GET /_search
    {
        "query": {
            "bool": {
                "must": {
                    "match": {
                        "content": { 
                            "query":    "full text search",
                            "operator": "and"
                        }
                    }
                },
                "should": [ 
                    { "match": { "content": "Elasticsearch" }},
                    { "match": { "content": "Lucene"        }}
                ]
            }
        }
    }

      * content 字段必须包含 full 、 text 和 search 所有三个词。

      * 如果 content 字段也包含 Elasticsearch 或 Lucene ,文档会获得更高的评分 _score 。

      should 语句匹配得越多表示文档的相关度越高。目前为止还挺好。 但是如果我们想让包含 Lucene 的有更高的权重,并且包含 Elasticsearch 的语句比 Lucene 的权重更高,该如何处理?

      我们可以通过指定 boost 来控制任何查询语句的相对的权重, boost 的默认值为 1 ,大于 1 会提升一个语句的相对权重。所以下面重写之前的查询:

    GET /_search
    {
        "query": {
            "bool": {
                "must": {
                    "match": {  
                        "content": {
                            "query":    "full text search",
                            "operator": "and"
                        }
                    }
                },
                "should": [
                    { "match": {
                        "content": {
                            "query": "Elasticsearch",
                            "boost": 3 
                        }
                    }},
                    { "match": {
                        "content": {
                            "query": "Lucene",
                            "boost": 2 
                        }
                    }}
                ]
            }
        }
    }

      * 这些语句使用默认的 boost 值 1 。

      * 这条语句更为重要,因为它有最高的 boost 值。

      * 这条语句比使用默认值的更重要,但它的重要性不及 Elasticsearch 语句。

      boost 参数被用来提升一个语句的相对权重( boost 值大于 1 )或降低相对权重( boost 值处于 0 到 1 之间),但是这种提升或降低并不是线性的,换句话说,如果一个 boost 值为 2 ,并不能获得两倍的评分 _score 。

      相反,新的评分 _score 会在应用权重提升之后被 归一化 ,每种类型的查询都有自己的归一算法,细节超出了本书的范围,所以不作介绍。

      简单的说,更高的 boost 值为我们带来更高的评分 _score 。 如果不基于 TF/IDF 要实现自己的评分模型,我们就需要对权重提升的过程能有更多控制,可以使用 function_score 查询操纵一个文档的权重提升方式而跳过归一化这一步骤。

  • 相关阅读:
    绿色通道
    Banknotes
    旅行问题
    修剪草坪
    最大连续和
    动物园
    炮兵阵地
    涂抹果酱
    牧场的安排
    国王
  • 原文地址:https://www.cnblogs.com/huanshilang/p/14139876.html
Copyright © 2020-2023  润新知