《Elasticsearch检索引擎学习指南》第七章 请求体查询
参考资料
请求体查询
建议查询–query-string search–对于用命令进行即席查询(ad-hoc)是非常有用的。然而,为了充分利用查询的强大功能,你应该使用请求体search API,之所以称之为请求体查询(Full-body search),因为大部分参数是通过http请求体而非查询字符串来传递。
空查询
空查询将返回所有索引库(indices)中的所有文档:
GET /_search
{}
一个空的请求体
只用查询字符串,就可以在一个、多个或者_all索引库(indices)和一个、多个或者所有types中查询:
GET /index_2014*/type1,type2/_search
{}
同时可以使用from和size参数来分页
GET /_search
{
"from": 30,
"size": 10
}
一个带请求体的GET请求?
某些特定语言(特别是JS)的http库是不允许GET请求带有请求体的。
而事实是RFC7231文档(专门负责处理HTTP语义和内容)并没有规定一个带有请求体的GET请求应该如何处理。结果是,一下HTTP服务器运行,而有一些(例如一些用于缓存和代理的服务器)–则不允许。
对于一个查询请求,ES的工程师偏向于使用GET方式,然而,因为带请求体的GET请求并不被广泛支持,所以search API同时支持POST请求
类似的规则可言应用于任何需要带请求体的GET API。
相对于使用晦涩难懂的查询字符串方式,一个带请求体的查询允许我们使用查询领域特定语言(query domain-specific language)或者Query DSL来查询语句。
查询表达式
查询表达式(Query DSL)是一种非常灵活又富有表现力的查询语言。ES使用它可以用简单的Json接口来展现Lucene功能的大部分。
要使用这种查询表达式,只需将查询语句传递给query参数:
GET /_search
{
"query": YOUR_QUERY_HERE
}
空查询在功能上等价于使用match_all查询,正如其名字,匹配所有文档:
GET /_search
{
"query": {
"match_all": {}
}
}
查询语句的结构
一个查询语句的典型结构:
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
如果是针对某个字段,那么它的结构如下:
{
QUERY_NAME: {
FIELD_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
}
举个例子,你可以使用match查询语句来查询tweet字段中包含ES的tweet:
{
"match": {
"tweet": "elasticsearch"
}
}
完整的查询请求如下:
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
}
}
合并查询语句
查询语句(Query clauses)就像一些简单的组合块,这些组合块可以彼此之间合并组成更复杂的查询。
这些语句可以是如下形式:
- 叶子语句(Leaf clauses)被用于将查询字符串和一个字段(或者多个字段)对比。
- 复合(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
}
}
到目前为止,你不必太在意这个例子的细节,我们会在后面详细解释。最重要的是你要理解到,一条复合语句可以将多条语句 — 叶子语句和其它复合语句 — 合并成一个单一的查询语句。
查询与过滤
ES使用的查询语言(DSL)拥有一套查询组件,这些组件可以以无限组合的方式进行搭配。这套组件可以在以下两种情况下使用:过滤情况(filtering context)和查询情况(query context)。
当使用于过滤情况时,查询被设置成一个“不评分”或者过滤查询。即,这个查询只是简单的问一个问题:这篇文档是否匹配。结果只有是与否
当使用于查询情况时,查询就变成了一个“评分”的查询。关注匹配程度。一个评分查询计算每一个文档与此查询的相关程度,同时将这个相关程度分派给表示相关性的字段_source ,并且按照相关性对匹配的文档进行排序。这种相关性的概念是非常适合全文搜索的情况,因为全文搜索几乎没有完全“正确”的答案。
性能差异
过滤查询因为判断规则简单,结果数量较少,不需要评分,所以有各种各样的手段进行优化(如将结果进行缓存,便于快速读取)。
而评分查询的结果不进行缓存。
过滤(filtering)的目标是减少那些需要通过评分查询(scoring queries)进行检查的文档。
如何选择查询和过滤
通常的规则是,使用查询语句来进行全文搜索或者其他任何需要影响相关性得分的搜索。除此之外的情况都使用过滤。
最重要的查询
虽然ES自带了很多的查询,但是经常用到的只有几个。下面简单介绍。
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" }}
TIP
:对于精确值的查询,可能需要使用filter语句来替代query,因为filter将会被缓存。下面有一些关于filter的例子。
match查询只是去查找给定的单词。这就意味着将查询字段暴露是安全的。
multi_match
可以在多个字段上执行相同的match查询:
{
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}
range
range 查询找出那些落在指定区间内的数字或者时间:(在数值条件范围内查询,可被比较与排序)
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
被允许的操作符如下:
- gt (greater than)大于
- 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查询
exist查询和missing查询被用于查找那些指定字段中有值或无值的文档。
{
"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" }}}
]
}
}
tip
:如果没有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" }}
}
}
}
如果需要通过多个不同的标准来过滤文档,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" }
}
}
}
term 查询被放置在 constant_score 中,转成不评分的 filter。这种方式可以用来取代只有 filter 语句的 bool 查询。