组合查询
组合多查询
现实的查询需求从来都没有那么简单;它们需要在多个字段上查询多种多样的文本,并且根据一系列的标准来过滤。为了构建类似的高级查询,你需要一种能够将多查询组合成单一查询的查询方法。
你可以用 bool 查询来实现你的需求。这种查询将多查询组合在一起,成为用户自己想要的布尔查询。它接收以下参数:
- must
- 文档 必须 匹配这些条件才能被包含进来。
- must_not
- 文档 必须不 匹配这些条件才能被包含进来。
- should
- 如果满足这些语句中的任意语句,将增加 _score ,否则,无任何影响。它们主要用于修正每个文档的相关性得分。
- filter
- 必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。
由于这是我们看到的第一个包含多个查询的查询,所以有必要讨论一下相关性得分是如何组合的。每一个子查询都独自地计算文档的相关性得分。一旦他们的得分被计算出来, bool 查询就将这些得分进行合并并且返回一个代表整个布尔操作的得分。
此查询将匹配满足以下所有条件的文档:
- title字段包含单词how go to
- content字段包含单词Elasticsearch。
- status字段包含"published"的确切单词。
- 发布日期字段包含自2015年1月1日起的日期。
GET /_search { "query": { "bool": { "must": [ { "match": { "title": "how go to" }}, { "match": { "content": "Elasticsearch" }} ], "filter": [ { "term": { "status": "published" }}, { "range": { "publish_date": { "gte": "2015/01/01" }}} ] } } }
下面的查询用于
- 查找 title 字段匹配 how to make millions
- 并且不被标识为 spam 的文档。
- 那些被标识为 starred 或在2014之后的文档,将比另外那些文档拥有更高的排名。如果 两者 都满足,那么它排名将更高:
GET /_search { "query": { "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }}, { "range": { "date": { "gte": "2014/01/01" }}} ] } } }
事先需要建一条记录试试:
POST /blog/_doc { "title": "how Search go to make millions", "content": "content Elasticsearch", "tag": "not spam", "status": "published", "publish_date": "2015/02/01", "date": "2015/01/01" }
复合查询说明
复合查询包装其他复合查询或叶子查询,可以组合其结果和分数,更改其行为,或者从查询切换到筛选上下文。
评分
以下查询有一个match_all查询,该查询为所有文档分配1.0的分数。
GET _search { "query": { "bool": { "must": { "match_all": {} }, "filter": { "term": { "status": "published" } } } } } or curl -XGET "http://localhost:9200/_search" -H 'Content-Type: application/json' -d'{ "query": { "bool": { "must": { "match_all": {} }, "filter": { "term": { "status": "published" } } } }}'
这个常量得分查询的行为方式与上面的示例完全相同。constant_score查询为筛选器匹配的所有文档分配1.0的分数。
GET _search { "query": { "constant_score": { "filter": { "term": { "status": "active" } } } } }
由于未指定评分查询,查询为所有文档分配0分:
GET _search { "query": { "bool": { "filter": { "term": { "status": "published" } } } } }
Boosting query
返回与正查询匹配的文档,同时减少也与负查询匹配的文档的相关性分数。
可以使用boosting查询降级某些文档,而不将其从搜索结果中排除。
GET /_search { "query": { "boosting" : { "positive" : { "term" : { "text" : "apple" } }, "negative" : { "term" : { "text" : "pie tart fruit crumble tree" } }, "negative_boost" : 0.5 } } }
测试数据
POST /blog/_doc { "text": "how to search pie and tree" } POST /blog/_doc { "text": "how to search apple and pie and tree" } POST /blog/_doc { "text": "how to search apple and apple tree" } POST /blog/_doc { "text": "how to search apple and apple bar" } POST /blog/_doc { "text": "my name is apple" } POST /blog/_doc { "text": "how to search apple and pie and tree pie tart" }
结果:
含有“apple”的分高,含有"pie tart fruit crumble tree"的分数低,包含的越多,分数越低
{ "took" : 922, "timed_out" : false, "_shards" : { "total" : 10, "successful" : 10, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 5, "relation" : "eq" }, "max_score" : 0.9642221, "hits" : [ { "_index" : "blog", "_type" : "_doc", "_id" : "f78jg28BdESjTDDpG97d", "_score" : 0.9642221, "_source" : { "text" : "how to search apple and apple tree" } }, { "_index" : "blog", "_type" : "_doc", "_id" : "gL8jg28BdESjTDDpKd7F", "_score" : 0.9642221, "_source" : { "text" : "how to search apple and apple bar" } }, { "_index" : "blog", "_type" : "_doc", "_id" : "gb8jg28BdESjTDDpNN5f", "_score" : 0.8504159, "_source" : { "text" : "my name is apple" } }, { "_index" : "blog", "_type" : "_doc", "_id" : "fr8jg28BdESjTDDpFd5h", "_score" : 0.6669828, "_source" : { "text" : "how to search apple and pie and tree" } }, { "_index" : "blog", "_type" : "_doc", "_id" : "gr8lg28BdESjTDDpsN4K", "_score" : 0.6020521, "_source" : { "text" : "how to search apple and pie and tree pie tart" } } ] } }
Constant score常数分数查询
包装筛选器查询并返回每个匹配文档,相关度得分等于boost参数值。
以下相关的分数均为1.2
GET /_search { "query": { "constant_score" : { "filter" : { "term" : { "text" : "apple"} }, "boost" : 1.2 } } } GET /_search { "query": { "dis_max" : { "queries" : [ { "term" : { "title" : "Quick pets" }}, { "term" : { "body" : "Quick pets" }} ], "tie_breaker" : 0.7 } } } POST /blog/_doc { "title": "Quick pets", "body": "how to search Quick pets" } POST /blog/_doc { "title": "Quick pets", "body": "how to search " } POST /blog/_doc { "title": "Quick pets", "body": "how to search pets" } POST /blog/_doc { "title": "how to search", "body": "how to search pets" } POST /blog/_doc { "title": "how to search", "body": "how to search Quick pets" }
精确查询
当进行精确值查找时, 我们会使用过滤器(filters)。过滤器很重要,因为它们执行速度非常快,不会计算相关度(直接跳过了整个评分阶段)而且很容易被缓存。
term 查询数字
我们首先来看最为常用的 term 查询, 可以用它处理数字(numbers)、布尔值(Booleans)、日期(dates)以及文本(text)。
数据准备:
POST /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 查询会查找我们指定的精确值。
GET /_search { "query": { "term" : { "price" : 20 } }}
通常当查找一个精确值的时候,我们不希望对查询进行评分计算。只希望对文档进行包括或排除的计算,所以我们会使用 constant_score 查询以非评分模式来执行 term 查询并以一作为统一评分。
最终组合的结果是一个 constant_score 查询,它包含一个 term 查询:
GET /products/_search { "query" : { "constant_score" : { "filter" : { "term" : { "price" : 20 } } } } }
查询置于 filter 语句内不进行评分或相关度的计算,所以所有的结果都会返回一个默认评分 1 。
如本部分开始处提到过的一样 ,使用 term 查询匹配字符串和匹配数字一样容易。如果我们想要查询某个具体 UPC ID 的产品,使用 SQL 表达式会是如下这样:
SELECT product FROM products WHERE productID = "XHDK-A-1293-#fJ3"
转换成查询表达式(query DSL),同样使用 term 查询,形式如下:
GET /products/_search { "query" : { "constant_score" : { "filter" : { "term" : { "productID" : "XHDK-A-1293-#fJ3" } } } } }
但这里有个小问题:我们无法获得期望的结果。为什么呢?问题不在 term 查询,而在于索引数据的方式。 如果我们使用 analyze API (分析 API),我们可以看到这里的 UPC 码被拆分成多个更小的 token :
GET /products/_analyze { "field": "productID", "text": "XHDK-A-1293-#fJ3" }
这里有几点需要注意:
- Elasticsearch 用 4 个不同的 token 而不是单个 token 来表示这个 UPC 。
- 所有字母都是小写的。
- 丢失了连字符和哈希符( # )。
- 所以当我们用 term 查询查找精确值 XHDK-A-1293-#fJ3 的时候,找不到任何文档,因为它并不在我们的倒排索引中,正如前面呈现出的分析结果,索引里有四个 token 。
显然这种对 ID 码或其他任何精确值的处理方式并不是我们想要的。
为了避免这种问题,我们需要告诉 Elasticsearch 该字段具有精确值,要将其设置成 not_analyzed 无需分析的。
组合过滤
比方说,怎样用 Elasticsearch 来表达下面的 SQL ?
SELECT product FROM products WHERE (price = 20 OR productID = "XHDK-A-1293-#fJ3") AND (price != 30)
这种情况下,我们需要 bool (布尔)过滤器。 这是个 复合过滤器(compound filter) ,它可以接受多个其他过滤器作为参数,并将这些过滤器结合成各式各样的布尔(逻辑)组合。
- must
- 所有的语句都 必须(must) 匹配,与 AND 等价。
- must_not
- 所有的语句都 不能(must not) 匹配,与 NOT 等价。
- should
- 至少有一个语句要匹配,与 OR 等价。
请注意,过滤和搜索的should是有区别的,搜索中should只是影响评分。而过滤是or的关系。
GET /products/_search { "query" : { "bool" : { "should" : [ { "term" : {"price" : 20}}, { "term" : {"productID" : "XHDK-A-1293-#fJ3"}} ], "must_not" : { "term" : {"price" : 30} } } } }
查找多个精确值
term 查询对于查找单个值非常有用,但通常我们可能想搜索多个值。 如果我们想要查找价格字段值为 $20 或 $30 的文档该如何处理呢?
GET /products/_search { "query" : { "constant_score" : { "filter" : { "terms" : { "price" : [20, 30] } } } } }