Elasticseach Query DSL

原创
2016/03/14 22:24
阅读数 2.2K

1.概述

在阐述Elasticsearch API介绍search相关内容时,只是简单介绍了查询请求的“报文体中提交了一个字段名为 query 用以表示查询条件的字段信息。该字段也是整个检索查询中最重要的提交信息”,然后就没有然后了。并没有深入地说明query字段对应的内容是什么样的。原因当然不是由于其中内容十分简单而忽略的,相反,正是因为其中内容相当地丰富所以就暂略去不表了。本文将正式介绍该方面的内容。

Elasticsearch提供了一种基于JSON的Query DSL来定义查询。其实前面说的query字段的值就是由这种Query DSL定义出的对象。可以将这种Query DSL认为是一种关于查询的抽象语法树(AST),它包含了两种类型的子句:

  • 叶子查询子句:定义特定字段上的特定查询条件,比如match term range查询。
  • 复合查询子句:借助逻辑运算来包装、组合其它叶子查询或复合查询。

查询子句可以工作在两种上下文中,query contextfilter context。这将决定其不同的执行方式和效果。

2.Query Context与Filter Context

  • Query Context
    • 在此上下文中,查询的问题场景是:“文档对查询条件的匹配程度如何?”所以除了确定文档是否匹配检索之外,还要计算出一个名为_score的分值,来表示文档的匹配程度。
    • 当查询子句被定义在名为query的字段上,此时就会生效Query Context。
  • Filter Context
    • 在此上下文中,查询的问题场景是:“文档是否匹配查询?”如此一来,答案就只是“是”和“否”的问题,不再有计算相关度得分的问题。Filter上下文多被用于过滤结构化的数据,比如:
      • timestamp是否在2015-2016范围内?
      • status是否为published
    • 频繁使用的filter会自动地被ES缓存,以增强性能。(因为不涉及相关度评分,所以结果可以被缓存)
    • 当查询子句被定义在名为filter的字段上时,此时就会生效Filter Context。比如bool查询中的filter参数、constant_score中的filter参数、filter aggregation以及bool查询的must_not参数。

举个例子:

GET _search
{
  "query": { (1) 
    "bool": {  (2)
      "must": [
        { "match": { "title":   "Search"        }},  (3)
        { "match": { "content": "Elasticsearch" }}   (4)
      ],
      "filter": [ (5)
        { "term":  { "status": "published" }}, (6)
        { "range": { "publish_date": { "gte": "2015-01-01" }}} (7)
      ]
    }
  }
}

(3)、(4)在query context下执行,(6)、(7)在filter context下执行。

3.查询子句

就像前文所述,Query DSL可以被认为是一种AST。那么树中节点可以分为“枝”和“叶”两种节点。叶节点的角色是定义具体的查询子句,而枝节点起到逻辑连接作用。

最小的一棵树只需包括一个叶节点,如下:

"query" : {
    "match_all" : {}
}

上例中可以看做是由关键词query引导定义了一棵查询树,树中仅包括一个查询子句match_all。查询子句的结构从上例中也可以看得比较清楚,查询类型是一个关键词同时也作为字段值,该字段对应的数据对象中可以再具体定义查询相关参数。上例中使用的是缺省参数,所以是一个空对象的表示。

总体而言,相对重要并且常用的查询子句包括如下内容:

输入图片说明

接下来将逐一介绍有关的各种类型的查询子句。

3.1 Match All 查询

Match All查询是一种比较特殊的查询,也是最简单的一种查询。它的语义就是匹配所有的文档,所有文档的相关度评分都一样,_score都直接设为1.0。也可以通过一个参数来设定评分值:

{ "match_all": { "boost" : 1.2 }}

如上,就可以通过boost参数设置所有文档的_score分值为1.2

3.2 全文查询

全文查询的应用场景多集中于针对一些全文本的数据字段,比如邮件正文、博客正文等。全文查询知晓字段内容是如何被分析的,并且会在查询执行之前首先将分析过程作用于查询输入。

“全文查询”可以再作以下分类:

  • match 查询
    • 实施全文查询的标准做法,包括fuzzy matchingphrase查询、proximity查询。
  • multi_match 查询
    • 普通match查询的多字段版本。
  • common_terms 查询
    • 一种针对罕见词,提供更多可定制化操作的较专业的查询方法。
  • query_string 查询
  • simple_query_string 查询:
    • 一种对query_string查询的改进,使得query_string的语法更健壮,更适合普通用户使用。

3.2.1 Match Query

Match查询只是一类查询的统称,这类查询可以接收text/numerics/dates数据类型,然后进行分析过程,最后构造出一个查询。比如下例所示:

{
    "match" : {
        "message" : "this is a test"
    }
}

上例中,使用关键词match定义了一个查询子句。match标明了该查询子句类型,其中内容message是一个字段的名称,表示查询作用的目标字段。它可以是任何合法的字段名称,也可以是_all

对于以上展示的match查询,经过设置不同参数或变体,可以有以下三种细分类型:

3.2.1.1 boolean match

默认地,就像前段举的例子那样,match就是boolean类型的。其背后的含义是,match会将搜索输入进行分析,分析结果就是得到一个布尔查询。对此可简单理解为:“this is a test”,被分析为[“this”, “test”],然后检索就变成了对于“this”和“test”的两个词的联合查询,默认的布尔连接操作符是or。不过,可以通过参数来改变布尔操作,比如下例:

{
    "match" : {
        "message" : {
            "query" : "this is a test",
            "operator" : "and"
        }
    }
}

关于检索输入的分析过程,如果不特殊指定的话,会缺省使用相应字段对应的分析器。

3.2.1.2 phrase match

phrase顾名思义是短语查询,意思在于在文档中搜索一个短语而不是分析后的词项。其使用方法如下:

{
    "match_phrase" : {
        "message" : "this is a test"
    }
}

其实match_phrase只是match的一种特殊种类,所以上述请求也可以写成下面的样子:

{
    "match" : {
        "message" : {
            "query" : "this is a test",
            "type" : "phrase"
        }
    }
}

match_phrase的控制有一个关键参数叫slop。该参数设置了短语被允许的“松散程度”。举个例子,默认slop=0,那么“word_a word_b word_c”只能匹配到“word_a word_b word_c”,如果匹配“word_a word_x word_b word_c”则将匹配失败。此时如果设置slop=1,那么上述情况就会反转,就会匹配成功。slop参数也可以被直接理解为“短语中允许被插入词的数量”。比如,如果想让“word_a word_x word_b word_c”匹配“word_a word_x word_b word_y word_c”成功,那么需要设置slop为一个不小于2的数值。顺便说一句,slop除了影响匹配精确度,也会拖慢匹配响应速度。

3.2.1.3 match_phrase_prefix

match_phrase_prefixmatch_phrase相同,除了它的操作是在短语的最后一个词项上使用prefix查询。如下:

{
    "match_phrase_prefix" : {
        "message" : "this is a test"
    }
}

或者:

{
    "match" : {
        "message" : {
            "query" : "this is a test",
            "type" : "phrase_prefix"
        }
    }
}

他除了接受像phrase_match一样的参数之外,还能个设置像prefix query一样的参数max_extensions,设置尝试扩展词的最大数量。(prefix在ES执行时,先试着确定扩展词,然后再根据扩展词来查询。那么,尝试的扩展词数量越多,结果就越丰富,但是耗时就越多。)

3.2.2 Multi Match

multi_match就是普通match的多字段版本。普通的match只能设置为针对一个字段的查询,而multi_match则可以是以下这样:

{
  "multi_match" : {
    "query":    "this is a test", 
    "fields": [ "subject", "message" ] 
  }
}

字段不仅仅可以是简单数组,还可以通配字段名称:

{
  "multi_match" : {
    "query":    "Will Smith",
    "fields": [ "title", "*_name" ] 
  }
}

其中*_name就可以通配first_namelast_name

然后,字段的检索权重还可以区别设置:

{
  "multi_match" : {
    "query" : "this is a test",
    "fields" : [ "subject^3", "message" ] 
  }
}

以上表示subject字段的权重是message字段的三倍。

多字段检索过程中会涉及到相关度评分规则问题,因为涉及了多个字段,所以检索的初步结果会是针对各个字段的检索结果相关度评分,那么怎么得出最终的相关度评分呢?这是根据具体的多字段检索类型来判断的。通过为multi_matchtype参数设置不同的值,以确定不同的最终评分策略:

参数名称 描述
best_fields (default) Finds documents which match any field, but uses the _score from the best field.
most_fields Finds documents which match any field and combines the _score from each field.
cross_fields Treats fields with the same analyzer as though they were one big field. Looks for each word in any field.
phrase Runs a match_phrase query on each field and combines the _score from each field.
phrase_prefix Runs a match_phrase_prefix query on each field and combines the _score from each field.

以上类型,除了cross_fields其它的都已经解释地比较清楚。那么cross_fields究竟是什么意思呢?

cross顾名思义就是“交叉”的意思。以下例子:

{
  "multi_match" : {
    "query":      "Will Smith",
    "type":       "cross_fields",
    "fields":     [ "first_name", "last_name" ]
  }
}

它的执行目标就是,willsmith两个term必须至少出现在first_namelast_name两字段的其中一个上。

3.2.3 Common Terms Query

common查询的应用场景在于检索里包括的词项中有stopword,但是这里的stopword由于其语境不同所以不再是通常意义的stopword,相反,必须将该词项考虑到检索和计分中去。使用common查询将可以完成该项工作,并且不会牺牲掉检索性能。

比如说,检索的内容是to be or not to be

这里大致说明common查询的做法。common查询将查询词项分成了两组:more important(低频词项)和 less important (高频词项,比如本来该是stopword的词项)。

它会首先检索more important分组的结果。这些词项出现的文档数量较少,并且对相关度评分影响较大。然后,在第一步检索结果的基础之上再检索less important分组中的词项。

以上过程中还会涉及到一些问题,比如如何划分什么是more important什么是less important的等等。可以有相关参数设置来控制。更多请见common terms query

3.2.4 Query String Query

Query String 查询就是直接将Lucene query string syntax支持的查询命令写成下面由ES支持的样子(命令内容写在`query_string.query上):

{
    "query_string" : {
        "default_field" : "content",
        "query" : "this AND that OR thus"
    }
}

query_string中,可以跟query平级的参数还有其它更多

3.2.5 Simple Query String Query

Simple Query String Query是对普通的Query String Query的简化性改进,他能够帮助使用者降低query_string语法的使用难度。具体可参见更多说明

3.3 Term级别查询

全文查询会在执行之前先分析查询文本。而Term级别查询是直接使用倒排索引中存储的term来操作查询。这种查询通常被用来查询结构化的数据,比如数字、日期、枚举等,而不再是全文字段。使用这种查询将绕过分析过程,直接在较低的层次来控制查询。

“Term查询”可以再做以下分类:

  • term query
    • 查询指定字段中包含指定term的文档
  • terms query
    • 查询指定字段中包含指定的多个term中任意一个的文档
  • range query
    • 查询指定字段的值在某个指定范围的文档
  • exists query
    • 查询指定字段值不为null的文档
  • missing query
    • exists query相对的查询
  • prefix query
    • 查询指定字段包括以指定prefix打头的term的文档
  • wildcard query
    • 查询指定字段中包含指定term的文档,指定term时可以使用通配符(single character wildcards (?) and multi-character wildcards (*) )
  • regexp query
    • 查询指定字段中包含指定term的文档,指定term时使用正则表达式来表示
  • fuzzy query
    • 查询指定字段中包含指定term的文档时不再精确地查询指定term,而是查找相似term(使用Levenshtein edit distance来判断)
  • type query
    • 查找属于定类型的文档
  • ids query
    • 查找属于特定类型,id为指定id的为文档。

3.3.1 Term query

term 查询的使用方法很简单,就像以下例子中查询user字段上包含Kimchyterm的文档。

{
    "term" : { "user" : "Kimchy" } 
}

3.3.2 Terms query

terms查询的使用方法如下例所示:

{
    "terms" : { "user" : ["kimchy", "elasticsearch"]}
}

为了简便,terms关键词也可以直接写成in

3.3.3 Range query

range查询的使用方法如下:

{
    "range" : {
        "age" : {
            "gte" : 10,
            "lte" : 20,
            "boost" : 2.0
        }
    }
}
range配置项 说明
gte Greater-than or equal to
gt Greater-than
lte Less-than or equal to
lt Less-than
boost Sets the boost value of the query, defaults to

在针对日期类型字段使用range查询时,可以使用Date Match表示方法。使用时也可以灵活运用Date Format:

{
    "range" : {
        "born" : {
            "gte": "01/01/2012",
            "lte": "2013",
            "format": "dd/MM/yyyy||yyyy"
        }
    }
}

并且也可以设置时区信息:

{
    "range" : {
        "timestamp" : {
            "gte": "2015-01-01 00:00:00", 
            "lte": "now", 
            "time_zone": "+01:00"
        }
    }
}

3.3.4 Exists query

使用方法如下:

{
    "exists" : { "field" : "user" }
}

针对以上查询,举例以下文档都会被匹配:

{ "user": "jane" }
{ "user": "" } 
{ "user": "-" } 
{ "user": ["jane"] }
{ "user": ["jane", null ] } 

而以下文档都不会被匹配:

{ "user": null }
{ "user": [] } 
{ "user": [null] } 
{ "foo":  "bar" } 

需要注意的是,在定义文档type时,null_value的设置会影响上述结果。具体请参见更多

3.3.4 Missing query

在ESv2.2.0版本以后已经被丢弃。因为它的功能跟exists是相对的所以,完全可以被以下方法取代:

"bool": {
    "must_not": {
        "exists": {
            "field": "user"
        }
    }
}

3.3.5 Prefix query

使用方法如下:

{
    "prefix" : { "user" : "ki" }
}

3.3.6 Wildcard query

该方法允许使用通配符来表示term,使用方法如下:

{
    "wildcard" : { "user" : "ki*y" }
}

3.3.7 Regexp query

该方法允许使用正则式来表示term,使用方法如下:

{
    "regexp":{
        "name.first": "s.*y"
    }
}

3.3.8 Fuzzy query

该模糊查询的手段是分别针对

  • 字符串类型采用“编辑距离”( Levenshtein edit distance )来计算字符串的匹配程度
  • 数值类型(数字、日期)采用算术差来计算匹配程度

针对字符串最简单用法就是:

{
    "fuzzy" : { "user" : "ki" }
}

还有更多可以控制的参数如下:

{
    "fuzzy" : {
        "user" : {
            "value" :         "ki",
            "boost" :         1.0,
            "fuzziness" :     2,
            "prefix_length" : 0,
            "max_expansions": 100
        }
    }
}

关于上述参数可见更多详细参考

针对数值类型的fuzzy查询就相对简单了许多:

{
    "fuzzy" : {
        "price" : {
            "value" : 12,
            "fuzziness" : 2
        }
    }
}

以上查询将会查找10-14之间的结果。当然,针对日期字段也可以直接用日期表达式:

{
    "fuzzy" : {
        "created" : {
            "value" : "2010-02-05T12:05:07",
            "fuzziness" : "1d"
        }
    }
}

3.3.9 Fuzzy query

type查询就是针对文档_type字段的查询。如下:

{
    "type" : {
        "value" : "my_type"
    }
}

3.3.10 Ids query

ids查询就是针对文档_uid字段的查询。如下:

{
    "ids" : {
        "type" : "my_type",
        "values" : ["1", "4", "100"]
    }
}

3.4 复合查询

前文介绍Query DSL包括两种类型的子句,第一种叶子子句已经在上文中一一进行了介绍,主要分为“全文查询”和“term查询”两大类。那么,另一种是复合子句。复合子句可以理解为是一种关于子句的“组合模式”。通过组合可以构建出一个关于查询的树形定义,如此之后,查询结果、相关度评分都是联合性的结果,另外还可以控制query contextfilter context之间的转换。

复合有以下若干种:

  • constant_score query
    • 只是简单的包装一下另一个查询,首先可以使得被包装的查询执行在filter context中,然后,所有的匹配文档给予一个“常量”得分。
  • bool query
    • 缺省的叶子查询组合方式,包括must should must_not filtermust should会联合考虑相关度评分,而must_notfilter执行在filter context下。
  • dis_max query
    • 组合多个查询,匹配任意查询的文档都会被包含在结果中。与普通的bool查询有区别的是,bool查询会联合所有的匹配相关度得分,而dis_max则仅使用最佳匹配得分。
  • function_score query
    • 可以用来修正查询的相关度评分。
  • boosting query
    • 可以通过positivenegative的设置来控制相关度评分。
  • indices query
    • 控制各个查询针对不同的索引。
  • and or not query
    • bool查询的别名。从v2.0.0版本之后被丢弃了。使用bool查询替代。
  • filtered query
    • 它的作用在于联合一个query context查询与一个filter context查询。从v2.0.0版本之后被丢弃了。使用bool查询替代。
  • limit query
    • 限制每个分片的文档检查数量。从v2.0.0版本之后被丢弃了。使用terminate_after 参数查询替代。

3.4.1 constant_score query

其用法如下:

{
    "constant_score" : {
        "filter" : {
            "term" : { "user" : "kimchy"}
        },
        "boost" : 1.2
    }
}

可以看到constant_score包装了要给filter,并且设置了boost参数给所有的匹配文档给予一个“常量”得分。

其实所有filter上下文得出的查询结果,文档相关度得分都是0,所以可以说是用constant_score来控制一下filter的得分(!=0)。

3.4.2 Bool Query

将多个查询子句进行布尔组合。每个字句需要被指定为一种特定的出现角色:

角色 描述
must 必须被满足,并且会影响相关度评分。The clause (query) must appear in matching documents and will contribute to the score.
filter 必须被满足,但是不会影响相关度评分。The clause (query) must appear in matching documents. However unlike must the score of the query will be ignored.
should 在没有must的情况下,必须有一个或多个被满足,至少被满足的数量是由一个参数minimum_should_match来控制的。The clause (query) should appear in the matching document. In a boolean query with no must clauses, one or more should clauses must match a document, the minimum number of should clauses to match can be set using the minimum_should_match parameter.
must_not must恰好相对。The clause (query) must not appear in the matching documents.

需要特别说明的是,如果bool查询的上下文是filter并且其中包含should子句,那么至少有一个should被满足。

bool查询采取“越多的匹配就越好”原则,所以mustshould的匹配相关度评分会被加和来得出最终的相关度得分。下面是一个应用实例:

{
    "bool" : {
        "must" : {
            "term" : { "user" : "kimchy" }
        },
        "filter": {
            "term" : { "tag" : "tech" }
        },
        "must_not" : {
            "range" : {
                "age" : { "from" : 10, "to" : 20 }
            }
        },
        "should" : [
            {
                "term" : { "tag" : "wow" }
            },
            {
                "term" : { "tag" : "elasticsearch" }
            }
        ],
        "minimum_should_match" : 1,
        "boost" : 1.0
    }
}

The bool query takes a more-matches-is-better approach, so the score from each matching must or should clause will be added together to provide the final _score for each document.

3.4.3 Dis Max Query

{
    "dis_max" : {
        "tie_breaker" : 0.7,
        "boost" : 1.2,
        "queries" : [
            {
                "term" : { "age" : 34 }
            },
            {
                "term" : { "age" : 35 }
            }
        ]
    }
}

像上例,Dis Max Query组合多个子查询,只是最终评分取所有子查询中的最大得分。参数tie_breaker用来控制以下情况:当同样的term出现在一个最大得分query中,和同时出现在多个query中,后者将会被评价为比前者更好的结果。

3.4.4 Function Score Query

使用function_score可以使用用户有机会修改查询结果中的相关度评分结果。

使用该API是一种相对高级的玩法,详细介绍请参考更多

3.4.5 Boosting Query

{
    "boosting" : {
        "positive" : {
            "term" : {
                "field1" : "value1"
            }
        },
        "negative" : {
            "term" : {
                "field2" : "value2"
            }
        },
        "negative_boost" : 0.2
    }
}

negative参数用来配置降分查询。

3.4.6 Indices Query

indices查询的作用在于提交一次请求,在该次请求中将索引分成两组,每组索引各执行 一种查询。

{
    "indices" : {
        "indices" : ["index1", "index2"],
        "query" : {
            "term" : { "tag" : "wow" }
        },
        "no_match_query" : {
            "term" : { "tag" : "kow" }
        }
    }
}

以上,query执行于index1 index2之上,而no_match_query执行于其它索引之上。

展开阅读全文
打赏
1
5 收藏
分享
加载中
YuMG博主

引用来自“枫中叶”的评论

能否把思维导图分享下 44
http://astah.net/

astah Professional
2016/09/18 09:48
回复
举报
能否把思维导图分享下 44
2016/09/14 09:03
回复
举报
更多评论
打赏
2 评论
5 收藏
1
分享
返回顶部
顶部