小烨收藏ElasticSearch权威指南-请求体查询
Posted SureData数烨数据
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小烨收藏ElasticSearch权威指南-请求体查询相关的知识,希望对你有一定的参考价值。
请求体查询
简易 查询 —query-string search— 对于用命令行进行点对点(ad-hoc)查询是非常有用的。 然而,为了充分利用查询的强大功能,你应该使用 请求体 search
API, 之所以称之为请求体查询(Full-Body Search),因为大部分参数是通过 Http 请求体而非查询字符串来传递的。
请求体查询 —下文简称 查询—不仅可以处理自身的查询请求,还允许你对结果进行片段强调(高亮)、对所有或部分结果进行聚合分析,同时还可以给出 你是不是想找 的建议,这些建议可以引导使用者快速找到他想要的结果。
空查询
让我们以 最简单的 search
API 的形式开启我们的旅程,空查询将返回所有索引库(indices)中的所有文档:
GET /_search {}
|
这是一个空的请求体。 |
只用一个查询字符串,你就可以在一个、多个或者 _all
索引库(indices)和一个、多个或者所有types中查询:
GET /index_2014*/type1,type2/_search {}
同时你可以使用 from
和 size
参数来分页:
GET /_search { "from": 30, "size": 10 }
一个带请求体的 GET 请求?
某些特定语言(特别是 javascript)的 HTTP 库是不允许 GET
请求带有请求体的。 事实上,一些使用者对于 GET
请求可以带请求体感到非常的吃惊。
而事实是这个RFC文档 RFC 7231— 一个专门负责处理 HTTP 语义和内容的文档 — 并没有规定一个带有请求体的 GET
请求应该如何处理!结果是,一些 HTTP 服务器允许这样子,而有一些 — 特别是一些用于缓存和代理的服务器 — 则不允许。
对于一个查询请求,Elasticsearch 的工程师偏向于使用 GET
方式,因为他们觉得它比 POST
能更好的描述信息检索(retrieving information)的行为。然而,因为带请求体的 GET
请求并不被广泛支持,所以 search
API 同时支持 POST
请求:
POST /_search { "from": 30, "size": 10 }
类似的规则可以应用于任何需要带请求体的 GET
API。
我们将在聚合 聚合 章节深入介绍聚合(aggregations),而现在,我们将聚焦在查询。
相对于使用晦涩难懂的查询字符串的方式,一个带请求体的查询允许我们使用 查询领域特定语言(query domain-specific language) 或者 Query DSL 来写查询语句。
查询表达式
查询表达式(Query DSL)是一种非常灵活又富有表现力的 查询语言。 Elasticsearch 使用它可以以简单的 JSON 接口来展现 Lucene 功能的绝大部分。在你的应用中,你应该用它来编写你的查询语句。它可以使你的查询语句更灵活、更精确、易读和易调试。
要使用这种查询表达式,只需将查询语句传递给 query
参数:
GET /_search { "query": YOUR_QUERY_HERE }
空查询(empty search) —{}
— 在功能上等价于使用 match_all
查询, 正如其名字一样,匹配所有文档:
GET /_search { "query": { "match_all": {} } }
查询语句的结构
一个查询语句 的典型结构:
{ QUERY_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } }
如果是针对某个字段,那么它的结构如下:
{ QUERY_NAME: { FIELD_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } }
举个例子,你可以使用 match
查询语句 来查询 tweet
字段中包含 elasticsearch
的 tweet:
{ "match": { "tweet": "elasticsearch" } }
完整的查询请求如下:
GET /_search { "query": { "match": { "tweet": "elasticsearch" } } }
合并查询语句
查询语句(Query clauses) 就像一些简单的组合块 ,这些组合块可以彼此之间合并组成更复杂的查询。这些语句可以是如下形式:
叶子语句(Leaf clauses) (就像
match
语句) 被用于将查询字符串和一个字段(或者多个字段)对比。复合(Compound) 语句 主要用于 合并其它查询语句。 比如,一个
bool
语句 允许在你需要的时候组合其它语句,无论是must
匹配、must_not
匹配还是should
匹配,同时它可以包含不评分的过滤器(filters):
{ "bool": { "must": { "match": { "tweet": "elasticsearch" }}, "must_not": { "match": { "name": "mary" }}, "should": { "match": { "tweet": "full text" }}, "filter": { "range": { "age" : { "gt" : 30 }} } } }
一条复合语句可以合并 任何 其它查询语句,包括复合语句,了解这一点是很重要的。这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。
例如,以下查询是为了找出信件正文包含 business opportunity
的星标邮件,或者在收件箱正文包含 business opportunity
的非垃圾邮件:
{ "bool": { "must": { "match": { "email": "business opportunity" }}, "should": [ { "match": { "starred": true }}, { "bool": { "must": { "match": { "folder": "inbox" }}, "must_not": { "match": { "spam": true }} }} ], "minimum_should_match": 1 } }
到目前为止,你不必太在意这个例子的细节,我们会在后面详细解释。最重要的是你要理解到,一条复合语句可以将多条语句 — 叶子语句和其它复合语句 — 合并成一个单一的查询语句。
查询与过滤
Elasticsearch 使用的查询语言(DSL) 拥有一套查询组件,这些组件可以以无限组合的方式进行搭配。这套组件可以在以下两种情况下使用:过滤情况(filtering context)和查询情况(query context)。
当使用于 过滤情况 时,查询被设置成一个“不评分”或者“过滤”查询。即,这个查询只是简单的问一个问题:“这篇文档是否匹配?”。回答也是非常的简单,yes 或者 no ,二者必居其一。
created
时间是否在2013
与2014
这个区间?status
字段是否包含published
这个单词?lat_lon
字段表示的位置是否在指定点的10km
范围内?
当使用于 查询情况 时,查询就变成了一个“评分”的查询。和不评分的查询类似,也要去判断这个文档是否匹配,同时它还需要判断这个文档匹配的有 _多好_(匹配程度如何)。 此查询的典型用法是用于查找以下文档:
查找与
full text search
这个词语最佳匹配的文档包含
run
这个词,也能匹配runs
、running
、jog
或者sprint
包含
quick
、brown
和fox
这几个词 — 词之间离的越近,文档相关性越高标有
lucene
、search
或者java
标签 — 标签越多,相关性越高
一个评分查询计算每一个文档与此查询的 _相关程度_,同时将这个相关程度分配给表示相关性的字段 `_score`,并且按照相关性对匹配到的文档进行排序。这种相关性的概念是非常适合全文搜索的情况,因为全文搜索几乎没有完全 “正确” 的答案。
自 Elasticsearch 问世以来,查询与过滤(queries and filters)就独自成为 Elasticsearch 的组件。但从 Elasticsearch 2.0 开始,过滤(filters)已经从技术上被排除了,同时所有的查询(queries)拥有变成不评分查询的能力。
然而,为了明确和简单,我们用 "filter" 这个词表示不评分、只过滤情况下的查询。你可以把 "filter" 、 "filtering query" 和 "non-scoring query" 这几个词视为相同的。
相似的,如果单独地不加任何修饰词地使用 "query" 这个词,我们指的是 "scoring query" 。
性能差异
过滤查询(Filtering queries)只是简单的检查包含或者排除,这就使得计算起来非常快。考虑到至少有一个过滤查询(filtering query)的结果是 “稀少的”(很少匹配的文档),并且经常使用不评分查询(non-scoring queries),结果会被缓存到内存中以便快速读取,所以有各种各样的手段来优化查询结果。
相反,评分查询(scoring queries)不仅仅要找出 匹配的文档,还要计算每个匹配文档的相关性,计算相关性使得它们比不评分查询费力的多。同时,查询结果并不缓存。
多亏倒排索引(inverted index),一个简单的评分查询在匹配少量文档时可能与一个涵盖百万文档的filter表现的一样好,甚至会更好。但是在一般情况下,一个filter 会比一个评分的query性能更优异,并且每次都表现的很稳定。
过滤(filtering)的目标是减少那些需要通过评分查询(scoring queries)进行检查的文档。
如何选择查询与过滤
通常的规则是,使用 查询(query)语句来进行 全文 搜索或者其它任何需要影响 相关性得分 的搜索。除此以外的情况都使用过滤(filters)。
最重要的查询
虽然 Elastidsearch 自带了很多的查询,但经常用到的也就那么几个。我们将在 深入搜索 章节详细讨论那些查询的细节,接下来我们对最重要的几个查询进行简单介绍。
match_all 查询
match_all
查询简单的 匹配所有文档。在没有指定查询方式时,它是默认的查询:
{ "match_all": {}}
它经常与 filter 结合使用--例如,检索收件箱里的所有邮件。所有邮件被认为具有相同的相关性,所以都将获得分值为 1
的中性 `_score`。
match 查询
无论你在任何字段上进行的是全文搜索还是精确查询,match
查询是你可用的标准查询。
如果你在一个全文字段上使用 match
查询,在执行查询前,它将用正确的分析器去分析查询字符串:
{ "match": { "tweet": "About Search" }}
如果在一个精确值的字段上使用它, 例如数字、日期、布尔或者一个 not_analyzed
字符串字段,那么它将会精确匹配给定的值:
{ "match": { "age": 26 }} { "match": { "date": "2014-09-01" }} { "match": { "public": true }} { "match": { "tag": "full_text" }}
对于精确值的查询,你可能需要使用 filter 语句来取代 query,因为 filter 将会被缓存。接下来,我们将看到一些关于 filter 的例子。
不像我们在 轻量 搜索 章节介绍的字符串查询(query-string search), match
查询不使用类似 +user_id:2 +tweet:search
的查询语法。它只是去查找给定的单词。这就意味着将查询字段暴露给你的用户是安全的;你需要控制那些允许被查询字段,不易于抛出语法异常。
multi_match 查询
multi_match
查询可以在多个字段上执行相同的 match
查询:
{ "multi_match": { "query": "full text search", "fields": [ "title", "body" ] } }
range 查询
range
查询找出那些落在指定区间内的数字或者时间:
{ "range": { "age": { "gte": 20, "lt": 30 } } }
被允许的操作符如下:
gt
大于
gte
大于等于
lt
小于
lte
小于等于
term 查询
term
查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些 not_analyzed
的字符串:
{ "term": { "age": 26 }} { "term": { "date": "2014-09-01" }} { "term": { "public": true }} { "term": { "tag": "full_text" }}
term
查询对于输入的文本不 分析 ,所以它将给定的值进行精确查询。
terms 查询
terms
查询和 term
查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}
和 term
查询一样,terms
查询对于输入的文本不分析。它查询那些精确匹配的值(包括在大小写、重音、空格等方面的差异)。
exists 查询和 missing 查询
exists
查询和 missing
查询被用于查找那些指定字段中有值 (exists
) 或无值 (missing
) 的文档。这与SQL中的 IS_NULL
(missing
) 和 NOT IS_NULL
(exists
) 在本质上具有共性:
{ "exists": { "field": "title" } }
这些查询经常用于某个字段有值的情况和某个字段缺值的情况。
组合多查询
现实的查询需求从来都没有那么简单;它们需要在多个字段上查询多种多样的文本,并且根据一系列的标准来过滤。为了构建类似的高级查询,你需要一种能够将多查询组合成单一查询的查询方法。
你可以用 bool
查询来实现你的需求。这种查询将多查询组合在一起,成为用户自己想要的布尔查询。它接收以下参数:
must
文档 必须 匹配这些条件才能被包含进来。
must_not
文档 必须不 匹配这些条件才能被包含进来。
should
如果满足这些语句中的任意语句,将增加
_score
,否则,无任何影响。它们主要用于修正每个文档的相关性得分。filter
必须 匹配,但它以不评分、过滤模式来进行。这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。
由于这是我们看到的第一个包含多个查询的查询,所以有必要讨论一下相关性得分是如何组合的。每一个子查询都独自地计算文档的相关性得分。一旦他们的得分被计算出来, bool
查询就将这些得分进行合并并且返回一个代表整个布尔操作的得分。
下面的查询用于查找 title
字段匹配 how to make millions
并且不被标识为 spam
的文档。那些被标识为 starred
或在2014之后的文档,将比另外那些文档拥有更高的排名。如果 _两者_ 都满足,那么它排名将更高:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }}, { "range": { "date": { "gte": "2014-01-01" }}} ] } }
如果没有 must
语句,那么至少需要能够匹配其中的一条 should
语句。但,如果存在至少一条 must
语句,则对 should
语句的匹配没有要求。
增加带过滤器(filtering)的查询
如果我们不想因为文档的时间而影响得分,可以用 filter
语句来重写前面的例子:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }} ], "filter": { "range": { "date": { "gte": "2014-01-01" }} } } }
|
range 查询已经从 |
通过将 range 查询移到 filter
语句中,我们将它转成不评分的查询,将不再影响文档的相关性排名。由于它现在是一个不评分的查询,可以使用各种对 filter 查询有效的优化手段来提升性能。
所有查询都可以借鉴这种方式。将查询移到 bool
查询的 filter
语句中,这样它就自动的转成一个不评分的 filter 了。
如果你需要通过多个不同的标准来过滤你的文档,bool
查询本身也可以被用做不评分的查询。简单地将它放置到 filter
语句中并在内部构建布尔逻辑:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }} ], "filter": { "bool": { "must": [ { "range": { "date": { "gte": "2014-01-01" }}}, { "range": { "price": { "lte": 29.99 }}} ], "must_not": [ { "term": { "category": "ebooks" }} ] } } } }
|
将 |
通过混合布尔查询,我们可以在我们的查询请求中灵活地编写 scoring 和 filtering 查询逻辑。
constant_score 查询
尽管没有 bool
查询使用这么频繁,constant_score
查询也是你工具箱里有用的查询工具。它将一个不变的常量评分应用于所有匹配的文档。它被经常用于你只需要执行一个 filter 而没有其它查询(例如,评分查询)的情况下。
可以使用它来取代只有 filter 语句的 bool
查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。
{ "constant_score": { "filter": { "term": { "category": "ebooks" } } } }
|
|
验证查询
查询可以变得非常的复杂,尤其 和不同的分析器与不同的字段映射结合时,理解起来就有点困难了。不过 validate-query
API 可以用来验证查询是否合法。
GET /gb/tweet/_validate/query { "query": { "tweet" : { "match" : "really powerful" } } }
以上 validate
请求的应答告诉我们这个查询是不合法的:
{ "valid" : false, "_shards" : { "total" : 1, "successful" : 1, "failed" : 0 } }
理解错误信息
为了找出 查询不合法的原因,可以将 explain
参数 加到查询字符串中:
GET /gb/tweet/_validate/query?explain { "query": { "tweet" : { "match" : "really powerful" } } }
|
|
很明显,我们将查询类型(match
)与字段名称 (tweet
)搞混了:
{ "valid" : false, "_shards" : { ... }, "explanations" : [ { "index" : "gb", "valid" : false, "error" : "org.elasticsearch.index.query.QueryParsingException: [gb] No query registered for [tweet]" } ] }
理解查询语句
对于合法查询,使用 explain
参数将返回可读的描述,这对准确理解 Elasticsearch 是如何解析你的 query 是非常有用的:
GET /_validate/query?explain { "query": { "match" : { "tweet" : "really powerful" } } }
我们查询的每一个 index 都会返回对应的 explanation
,因为每一个 index 都有自己的映射和分析器:
{ "valid" : true, "_shards" : { ... }, "explanations" : [ { "index" : "us", "valid" : true, "explanation" : "tweet:really tweet:powerful" }, { "index" : "gb", "valid" : true, "explanation" : "tweet:realli tweet:power" } ] }
从 explanation
中可以看出,匹配 really powerful
的 match
查询被重写为两个针对 tweet
字段的 single-term 查询,一个single-term查询对应查询字符串分出来的一个term。
当然,对于索引 us
,这两个 term 分别是 really
和 powerful
,而对于索引 gb
,term 则分别是 realli
和 power
。之所以出现这个情况,是由于我们将索引 gb
中 tweet
字段的分析器修改为 english
分析器。
一码不扫,
何以扫天下?
以上是关于小烨收藏ElasticSearch权威指南-请求体查询的主要内容,如果未能解决你的问题,请参考以下文章