ElasticSearch查询

一.空搜索

搜索API的最基础的形式是没有指定任何查询的空搜索,它简单地返回集群中所有索引下的所有文档

GET /_search
{
   "hits" : {
      "total" :       14,
      "hits" : [
        {
          "_index":   "us",
          "_type":    "tweet",
          "_id":      "7",
          "_score":   1,
          "_source": {
             "date":    "2014-09-17",
             "name":    "John Smith",
             "tweet":   "The Query DSL is really powerful and flexible",
             "user_id": 2
          }
       },
        ... 9 RESULTS REMOVED ...
      ],
      "max_score" :   1
   },
   "took" :           4,
   "_shards" : {
      "failed" :      0,
      "successful" :  10,
      "total" :       10
   },
   "timed_out" :      false
}
  • htis:返回结果中最重要的部分是 hits ,它包含total 字段来表示匹配到的文档总数,并且一个hits数组包含所查询结果的前十个文档。

  • _index:索引

  • _type:类型

  • _id: 文档Id

  • _score:衡量了文档与查询的匹配程度。默认情况下,首先返回最相关的文档结果,就是说,返回的文档是按照 _score降序排列的。

  • max_score:值是与查询所匹配文档的 _score 的最大值

  • took:执行整个搜索请求耗费了多少毫秒。

  • _shards:查询中参与分片的总数,以及这些分片成功了多少个失败了多少个。如果我们遭遇到一种灾难级别的故障,在这个故障中丢失了相同分片的原始数据和副本,那么对这个分片将没有可用副本来对搜索请求作出响应。假若这样,Elasticsearch 将报告这个分片是失败的,但是会继续返回剩余分片的结果。

  • timed_out:查询是否超时。默认情况下,搜索请求不会超时。如果低响应时间比完成结果更重要,你可以指定 timeout 为 10 或者 10ms(10毫秒),或者 1s(1秒):

    GET /_search?timeout=10ms
    

二.多索引,多类型

查询方式描述
/_search在所有的索引中搜索所有的类型
/gb,us/_search中搜索所有的类型在 gb 和 us 索引中搜索所有的文档
/g*,u*/_search在任何以 g 或者 u 开头的索引中搜索所有的类型
/gb/user/_search在 gb 索引中搜索 user 类型
/gb,us/user,tweet/_search在 gb 和 us 索引中搜索 user 和 tweet 类型

当在单一的索引下进行搜索的时候,Elasticsearch 转发请求到索引的每个分片中,可以是主分片也可以是副本分片,然后从每个分片中收集结果。多索引搜索恰好也是用相同的方式工作的—​只是会涉及到更多的分片。

三.分页

和 SQL 使用 LIMIT 关键字返回单个 page 结果的方法相同,Elasticsearch 接受 from 和 size 参数

  • size:显示应该返回的结果数量,默认是 10
  • from:显示应该跳过的初始结果数量,默认是0

如果每页展示 5 条结果,可以用下面方式请求得到 1 到 3 页的结果:

GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10

四.结构化搜索

4.1 精确值查找

4.1.1 term 查询数字

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

下面的例子开始介绍,创建并索引一些表示产品的文档,文档里有字段 priceproductID价格产品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 形式表达:

SELECT document FROM   products WHERE  price = 20

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

{
    "term" : {
        "price" : 20
    }
}

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

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

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

在这里插入图片描述

4.1.2 term查询文本

如本部分开始处提到过的一样 ,使用term 查询匹配字符串和匹配数字一样容易。如果我们想要查询某个具体 UPC ID 的产品,使用 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"
                }
            }
        }
    }
}

这里我们使用 analyze API (分析 API),我们可以看到这里的 UPC 码被拆分成多个更小的 token :

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

在这里插入图片描述
这里有几点需要注意:

  • Elasticsearch 用 4 个不同的 token 而不是单个 token 来表示这个 UPC 。
  • 所有字母都是小写的。
  • 丢失了连字符和哈希符( # )。

所以当我们用 term 查询查找精确值 XHDK-A-1293-#fJ3 的时候,找不到任何文档,因为它并不在我们的倒排索引中,正如前面呈现出的分析结果,索引里有四个 token 。
为了避免这种问题,我们需要告诉 Elasticsearch 该字段具有精确值,要将其设置成 not_analyzed 无需分析的。 我们可以在 自定义字段映射 中查看它的用法。为了修正搜索结果,我们需要首先删除旧索引(因为它的映射不再正确)然后创建一个能正确映射的新索引:

DELETE /my_store 

PUT /my_store 
{
    "mappings" : {
        "products" : {
            "properties" : {
                "productID" : {
                    "type" : "string",
                    "index" : "not_analyzed" 
                }
            }
        }
    }

}
  • 删除索引是必须的,因为我们不能更新已存在的映射。
  • 在索引被删除后,我们可以创建新的索引并为其指定自定义映射。
  • 这里我们告诉 Elasticsearch ,我们不想对 productID 做任何分析。

4.2 组合过滤器

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

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

4.2.1 布尔过滤器

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

{
   "bool" : {
      "must" :     [],
      "should" :   [],
      "must_not" : [],
   }
}
  • must:所有的语句都 必须(must) 匹配,与 AND 等价。
  • must_not:所有的语句都 不能(must not) 匹配,与 NOT 等价。
  • should:至少有一个语句要匹配,与 OR 等价。

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

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

4.2.2 嵌套布尔过滤器

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

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" : {
      "filtered" : {
         "filter" : {
            "bool" : {
              "should" : [
                { "term" : {"productID" : "KDKE-B-9947-#kL5"}}, 
                { "bool" : { 
                  "must" : [
                    { "term" : {"productID" : "JODL-X-1937-#pV7"}}, 
                    { "term" : {"price" : 30}} 
                  ]
                }}
              ]
           }
         }
      }
   }
}

4.3 查找多个精确值

term 查询对于查找单个值非常有用,但通常我们可能想搜索多个值。
不需要使用多个term查询,我们只要用单个terms查询(注意末尾的 s ), terms 查询好比是 term 查询的复数形式(以英语名词的单复数做比)。

{
    "terms" : {
        "price" : [20, 30]
    }
}

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

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

4.3.1 包含,而不是相等

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

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

Elasticsearch 会在倒排索引中查找包括某 term 的所有文档,然后构造一个 bitset 。在我们的例子中,倒排索引表如下:

TokenDocIDs
open_source2
searchsearch

term 查询匹配标记 search 时,它直接在倒排索引中找到记录并获取相关的文档 ID,如倒排索引所示,这里文档 1 和文档 2 均包含该标记,所以两个文档会同时作为结果返回。

4.3.2 精确相等

如果一定期望得到我们前面说的那种行为(即整个字段完全相等),最好的方式是增加并索引另一个字段, 这个字段用以存储该字段包含词项的数量,同样以上面提到的两个文档为例,现在我们包括了一个维护标签数的新字段:

{ "tags" : ["search"], "tag_count" : 1 }
{ "tags" : ["search", "open_source"], "tag_count" : 2 }

一旦增加这个用来索引项 term 数目信息的字段,我们就可以构造一个 constant_score 查询,来确保结果中的文档所包含的词项数量与要求是一致的:

GET /my_index/my_type/_search
{
    "query": {
        "constant_score" : {
            "filter" : {
                 "bool" : {
                    "must" : [
                        { "term" : { "tags" : "search" } },  #查找所有包含 term search 的文档。
                        { "term" : { "tag_count" : 1 } } #确保文档只有一个标签。
                    ]
                }
            }
        }
    }
}

4.4 范围

Elasticsearch 有 range 查询,不出所料地,可以用它来查找处于某个范围内的文档:

"range" : {
    "price" : {
        "gte" : 20,
        "lte" : 40
    }
}

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
                    }
                }
            }
        }
    }
}

4.4.1 日期范围

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 日 零时)

日期计算是 日历相关(calendar aware) 的,所以它不仅知道每月的具体天数,还知道某年的总天数(闰年)等信息。

4.4.2 字符串范围

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

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

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

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

4.5 处理Null值

有的文档有名为 tags (标签)的字段,它是个多值字段,一个文档可能有一个或多个标签,也可能根本就没有标签。如果一个字段没有值,那么如何将它存入倒排索引中的呢?
这是个有欺骗性的问题,因为答案是:什么都不存。让我们看看之前内容里提到过的倒排索引:

TokenDocIDs
open_source2
search1,2

一个倒排索引只是一个 token 列表和与之相关的文档信息,如果字段不存在,那么它也不会持有任何 token,也就无法在倒排索引结构中表现。
最终,这也就意味着,null, [] (空数组)和 [null] 所有这些都是等价的,它们无法存于倒排索引中。

4.5.1 存在查询

第一件武器就是 exists 存在查询。这个查询会返回那些在指定字段有任何值的文档

POST /my_index/posts/_bulk
{ "index": { "_id": "1"              }}
{ "tags" : ["search"]                }  
{ "index": { "_id": "2"              }}
{ "tags" : ["search", "open_source"] }  
{ "index": { "_id": "3"              }}
{ "other_field" : "some data"        }  
{ "index": { "_id": "4"              }}
{ "tags" : null                      }  
{ "index": { "_id": "5"              }}
{ "tags" : ["search", null]          }

以上文档集合中 tags 字段对应的倒排索引如下:

TokenDocIDs
open_source2
search1,2,5

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

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

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

这个查询返回 3 个文档:

"hits" : [
    {
      "_id" :     "1",
      "_score" :  1.0,
      "_source" : { "tags" : ["search"] }
    },
    {
      "_id" :     "5",
      "_score" :  1.0,
      "_source" : { "tags" : ["search", null] } 
    },
    {
      "_id" :     "2",
      "_score" :  1.0,
      "_source" : { "tags" : ["search", "open source"] }
    }
]

尽管文档 5 有 null 值,但它仍会被命中返回。字段之所以存在,是因为标签有实际值( search )可以被索引,所以 null 对过滤不会产生任何影响。

4.5.2 缺失查询

这个missing查询本质上与 exists 恰好相反:它返回某个特定 无 值字段的文档,与以下 SQL 表达的意思类似:

SELECT tags FROM   posts WHERE  tags IS NULL

我们将前面例子中 exists 查询换成 missing查询:

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

按照期望的那样,我们得到 3 和 4 两个文档(这两个文档的 tags 字段没有实际值):

"hits" : [
    {
      "_id" :     "3",
      "_score" :  1.0,
      "_source" : { "other_field" : "some data" }
    },
    {
      "_id" :     "4",
      "_score" :  1.0,
      "_source" : { "tags" : null }
    }
]

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

  • 它会匹配字段的类型,我们不能为一个 date 日期字段设置字符串类型的 null_value 。
  • 它必须与普通值不一样,这可以避免把实际值当成 null 空的情况。

4.5.3对象上的存在与缺失

不仅可以过滤核心类型, exists and missing 查询 还可以处理一个对象的内部字段。以下面文档为例:

{
   "name" : {
      "first" : "John",
      "last" :  "Smith"
   }
}

我们不仅可以检查 name.first 和 name.last 的存在性,也可以检查 name ,不过在 映射 中,如上对象的内部是个扁平的字段与值(field-value)的简单键值结构,类似下面这样:

{
   "name.first" : "John",
   "name.last"  : "Smith"
}

那么我们如何用 exists 或 missing 查询 name 字段呢? name 字段并不真实存在于倒排索引中。
原因是当我们执行下面这个过滤的时候:

{
    "exists" : { "field" : "name" }
}

实际执行的是:

{
    "bool": {
        "should": [
            { "exists": { "field": "name.first" }},
            { "exists": { "field": "name.last" }}
        ]
    }
}

这也就意味着,如果 first 和 last 都是空,那么 name 这个命名空间才会被认为不存在。

4.6 关于缓存

**过滤器的内部操作:**其核心实际是采用一个 bitset 记录与过滤器匹配的文档。Elasticsearch 积极地把这些 bitset 缓存起来以备随后使用。一旦缓存成功,bitset 可以复用 任何 已使用过的相同过滤器,而无需再次计算整个过滤器。这些 bitsets 缓存是“智能”的:它们以增量方式更新。当我们索引新文档时,只需将那些新文档加入已有 bitset,而不是对整个缓存一遍又一遍的重复计算。和系统其他部分一样,过滤器是实时的,我们无需担心缓存过期问题。

4.6.1独立的过滤器缓存

属于一个查询组件的 bitsets 是独立于它所属搜索请求其他部分的。这就意味着,一旦被缓存,一个查询可以被用作多个搜索请求。bitsets 并不依赖于它所存在的查询上下文。这样使得缓存可以加速查询中经常使用的部分,从而降低较少、易变的部分所带来的消耗。
同样,如果单个请求重用相同的非评分查询,它缓存的 bitset 可以被单个搜索里的所有实例所重用。
让我们看看下面例子中的查询,它查找满足以下任意一个条件的电子邮件:

  • 在收件箱中,且没有被读过的
  • 不在 收件箱中,但被标注重要的
GET /inbox/emails/_search
{
  "query": {
      "constant_score": {
          "filter": {
              "bool": {
                 "should": [
                    { "bool": {
                          "must": [
                             { "term": { "folder": "inbox" }}, 
                             { "term": { "read": false }}
                          ]
                    }},
                    { "bool": {
                          "must_not": {
                             "term": { "folder": "inbox" } 
                          },
                          "must": {
                             "term": { "important": true }
                          }
                    }}
                 ]
              }
            }
        }
    }
}

五.全文搜索

5.1匹配查询

匹配查询 match 是个 核心 查询。无论需要查询什么字段, match 查询都应该会是首选的查询方式。它是一个高级 全文查询 ,这表示它既能处理全文字段,又能处理精确字段。这就是说, match 查询主要的应用场景就是进行全文搜索。

5.1.1单个词查询

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

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

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

  1. 检查字段类型 。
    标题 title 字段是一个 string 类型( analyzed )已分析的全文字段,这意味着查询字符串本身也应该被分析。
  2. 分析查询字符串 。
    将查询的字符串 QUICK! 传入标准分析器中,输出的结果是单个项 quick 。因为只有一个单词项,所以 match查询执行的是单个底层 term 查询。
  3. 查找匹配文档 。
    term 查询在倒排索引中查找 quick 然后获取一组包含该项的文档,本例的结果是文档:1、2 和 3 。
  4. 为每个文档评分 。
    term 查询计算每个文档相关度评分 _score ,这是种将词频(term frequency,即词 quick 在相关文档的 title 字段中出现的频率)和反向文档频率(inverse document frequency,即词 quick 在所有文档的 title 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。

5.2 多词查询

如果我们一次只能搜索一个词,那么全文搜索就会不太灵活,幸运的是 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"
        }
     }
  ]
}

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

5.2.1 提高精度

用 任意 查询词项匹配文档可能会导致结果中出现不相关的长尾。这是种散弹式搜索。可能我们只想搜索包含 所有 词项的文档,也就是说,不去匹配 brown OR dog ,而通过匹配 brown AND dog 找到所有文档。

match 查询还可以接受 operator 操作符作为输入参数,默认情况下该操作符是 or 。我们可以将它修改成 and 让所有指定词项都必须匹配:

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

5.2.2 控制精度

在 所有 与 任意 间二选一有点过于非黑即白。如果用户给定 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% ,即三个里面两个词。无论这个值设置成什么,至少包含一个词项的文档才会被认为是匹配的。

5.3 组合查询

在 组合过滤器 中,我们讨论过如何使用 bool 过滤器通过 andornot 逻辑组合将多个过滤器进行组合。在查询中, 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"
        }
     }
  ]
}

5.3.1 评分计算

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

5.3.2 控制精度

所有 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” 。如果有文档包含所有三个条件,它会比只包含两个的文档更相关。

5.4 如何使用布尔匹配

目前为止,可能已经意识到多词 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 查询来表示,但是如果了解 match 内部的工作原理,我们就能根据自己的需要来控制查询过程。有些时候单个 match查询无法满足需求,比如为某些查询条件分配更高的权重。

5.5 查询语句提升权重

当然 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"        }}
            ]
        }
    }
}

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 参数被用来提升一个语句的相对权重( boost 值大于 1 )或降低相对权重( boost 值处于 0 到 1 之间),但是这种提升或降低并不是线性的,换句话说,如果一个 boost 值为 2 ,并不能获得两倍的评分 _score 。
相反,新的评分 _score 会在应用权重提升之后被 归一化 ,每种类型的查询都有自己的归一算法,细节超出了本书的范围,所以不作介绍。简单的说,更高的 boost 值为我们带来更高的评分 _score 。
如果不基于 TF/IDF 要实现自己的评分模型,我们就需要对权重提升的过程能有更多控制,可以使用 function_score 查询操纵一个文档的权重提升方式而跳过归一化这一步骤。

Elasticsearch官方文档: https://www.elastic.co/guide/cn/elasticsearch/guide/current/search-in-depth.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值