Elastic Search学习系列04-请求体查询
1 前言
轻量搜索—query-string search—对于用命令行进行即时查询(ad-hoc)是非常有用的。然而,为了充分利用查询的强大功能,你应该使用 请求体search API,之所以称之为请求体查询(Full-Body Search),因为大部分参数是通过Http请求体而非查询字符串来传递的。
请求体查询—下文简称查询,不仅可以处理自身的查询请求,还允许你对结果进行片段强调(高亮)、对所有或部分结果进行聚合分析,同时还可以给出你是不是想找的建议,这些建议可以引导使用者快速找到他想要的结果。
2 空查询
空查询将返回所有索引库(indices)中的所有文档:
curl -X GET "localhost:9200/_search?pretty" -H 'Content-Type: application/json' -d' {} '
可以在一个、多个或者_all索引库(indices)和一个、多个或者所有types中查询:
GET /index_2014*/type1,type2/_search {}
同时可以使用from和size参数来分页:
GET /_search { "from": 30, "size": 10 }
相对于使用晦涩难懂的查询字符串的方式,一个带请求体的查询允许我们使用查询领域特定语言(query domain-specific language)或者Query DSL来写查询语句。
3 查询表达式
查询表达式(Query DSL)是一种非常灵活又富有表现力的查询语言。Elasticsearch使用它可以以简单的JSON接口来展现Lucene功能的绝大部分。
要使用这种查询表达式,只需将查询语句传递给query参数:
GET /_search
{
"query": YOUR_QUERY_HERE
}
查询语句的结构
一个查询语句的典型结构:
{ QUERY_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } }
如果是针对某个字段,那么它的结构如下:
{ QUERY_NAME: { FIELD_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } }
合并查询语句
查询语句(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 }} } } }
一条复合语句可以合并任何其它查询语句,包括复合语句,了解这一点是很重要的。这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。
4 查询与过滤
Elasticsearch使用的查询语言(DSL)拥有一套查询组件,这些组件可以以无限组合的方式进行搭配。这套组件可以在以下两种情况下使用:过滤情况(filtering context)和查询情况(query context)。
当使用过滤情况时,查询被设置成一个“不评分”或者“过滤”查询。即,这个查询只是简单的问一个问题:“这篇文档是否匹配?”。回答也是非常的简单,yes或者no,二者必居其一。
当使用查询情况时,查询就变成了一个“评分”的查询。和不评分的查询类似,也要去判断这个文档是否匹配,同时它还需要判断这个文档匹配的有多好(匹配程度如何)。此查询的典型用法是用于查找以下文档:
- 查找与full text search这个词语最佳匹配的文档
- 包含run这个词,也能匹配runs、running、jog或者sprint
- 包含quick、brown和fox这几个词—词之间离的越近,文档相关性越高
- 标有lucene、search或者java标签—标签越多,相关性越高
一个评分查询计算每一个文档与此查询的相关程度,同时将这个相关程度分配给表示相关性的字段_score,并且按照相关性对匹配到的文档进行排序。这种相关性的概念是非常适合全文搜索的情况,因为全文搜索几乎没有完全“正确”的答案。
如果单独地不加任何修饰词地使用"query"这个词,我们指的是"scoring query"。
性能差异
过滤查询(Filtering queries)只是简单的检查包含或者排除,这就使得计算起来非常快。考虑到至少有一个过滤查询(filtering query)的结果是 “稀少的”(很少匹配的文档),并且经常使用不评分查询(non-scoring queries),结果会被缓存到内存中以便快速读取,所以有各种各样的手段来优化查询结果。
相反,评分查询(scoring queries)不仅仅要找出匹配的文档,还要计算每个匹配文档的相关性,计算相关性使得它们比不评分查询费力的多。同时,查询结果并不缓存。
多亏倒排索引(inverted index),一个简单的评分查询在匹配少量文档时可能与一个涵盖百万文档的filter表现的一样好,甚至会更好。但是在一般情况下,一个filter会比一个评分的query性能更优异,并且每次都表现的很稳定。
过滤(filtering)的目标是减少那些需要通过评分查询(scoring queries)进行检查的文档。
如何选择查询与过滤
通常的规则是,使用查询(query)语句来进行 全文 搜索或者其它任何需要影响 相关性得分 的搜索。除此以外的情况都使用过滤(filters)。
5 常用查询
5.1 match_all 查询
match_all 查询简单的匹配所有文档。在没有指定查询方式时,它是默认的查询:
{ "match_all": {}}
5.2 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将会被缓存。
5.3 multi_match 查询
multi_match查询可以在多个字段上执行相同的match查询:
{ "multi_match": { "query": "full text search", "fields": [ "title", "body" ] } }
5.4 range 查询
range查询找出那些落在指定区间内的数字或者时间:
{ "range": { "age": { "gte": 20, "lt": 30 } } }
被允许的操作符如下:
- gt 大于
- gte 大于等于
- lt 小于
- lte 小于等于
5.5 term 查询
term查询被用于精确值匹配,这些精确值可能是数字、时间、布尔或者那些not_analyzed的字符串:
{ "term": { "age": 26 }} { "term": { "date": "2014-09-01" }} { "term": { "public": true }} { "term": { "tag": "full_text" }}
term 查询对于输入的文本不 分析 ,所以它将给定的值进行精确查询。
5.6 terms 查询
terms查询和term查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}
和term查询一样,terms查询对于输入的文本不分析。它查询那些精确匹配的值(包括在大小写、重音、空格等方面的差异)。
5.7 exists 查询和 missing 查询
exists查询和missing查询被用于查找那些指定字段中有值(exists)或无值(missing)的文档。这与SQL中的IS_NULL(missing)和NOT IS_NULL(exists)在本质上具有共性:
{ "exists": { "field": "title" } }
这些查询经常用于某个字段有值的情况和某个字段缺值的情况。
6 组合查询
现实的查询需求从来都没有那么简单;它们需要在多个字段上查询多种多样的文本,并且根据一系列的标准来过滤。为了构建类似的高级查询,你需要一种能够将多查询组合成单一查询的查询方法。
你可以用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查询已经从should语句中移到filter语句。
通过将range查询移到filter语句中,我们将它转成不评分的查询,将不再影响文档的相关性排名。由于它现在是一个不评分的查询,可以使用各种对filter查询有效的优化手段来提升性能。
所有查询都可以借鉴这种方式。将查询移到bool查询的filter语句中,这样它就自动的转成一个不评分的filter了。
constant_score 查询
尽管没有bool查询使用这么频繁,constant_scor查询也是你工具箱里有用的查询工具。它将一个不变的常量评分应用于所有匹配的文档。它被经常用于你只需要执行一个filter而没有其它查询(例如,评分查询)的情况下。
可以使用它来取代只有filter语句的bool查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。
{ "constant_score": { "filter": { "term": { "category": "ebooks" } } } }
7 验证查询
查询可以变得非常的复杂,尤其和不同的分析器与不同的字段映射结合时,理解起来就有点困难了。不过validate-query API可以用来验证查询是否合法。
GET /gb/tweet/_validate/query { "query": { "tweet" : { "match" : "really powerful" } } }
以上 validate 请求的应答告诉我们这个查询是不合法的:
{
"valid" : false
}
理解错误信息
为了找出 查询不合法的原因,可以将explain参数加到查询字符串中:
GET /gb/tweet/_validate/query?explain { "query": { "tweet" : { "match" : "really powerful" } } }
explain 参数可以提供更多关于查询不合法的信息。
{ "valid" : false, "error" : "ParsingException[unknown query [tweet]]; nested: NamedObjectNotFoundException[[3:17] unknown field [tweet]];; org.elasticsearch.common.xcontent.NamedObjectNotFoundException: [3:17] unknown field [tweet]" }
理解查询语句
对于合法查询,使用explain参数将返回可读的描述,这对准确理解Elasticsearch 是如何解析你的query是非常有用的:
curl -X GET "localhost:9200/_validate/query?explain&pretty" -H 'Content-Type: application/json' -d' { "query": { "match" : { "tweet" : "really powerful" } } } '
我们查询的每一个index都会返回对应的explanation,因为每一个index都有自己的映射和分析器:
"explanations" : [ { "index" : ".apm-agent-configuration", "valid" : true, "explanation" : """MatchNoDocsQuery("unmapped fields [tweet]")""" }, { "index" : ".apm-custom-link", "valid" : true, "explanation" : """MatchNoDocsQuery("unmapped fields [tweet]")""" } ]