事实上关于结构化查询语句,我们可以使用两种结构化语句:结构化查询(Query DSL)和结构化过滤(Filter DSL)。查询与过滤语句非常相似,但是它们由于使用目的不同而稍有差异。
一条过滤语句会询问每个文档的字段值是否包含着特定值:
一条查询语句与过滤语句相似,但问法不同:
查询语句会询问每个文档的字段值与特定值的匹配程度如何?
查询语句的典型用法是为了找到文档:
一条查询语句会计算每个文档与查询语句的相关性,会给出一个相关性评分 _source,并且按照相关性对匹配到的文档进行排序。这种评分方式非常适用于一个没有完全配置结果的全文本搜索。
使用过滤语句得到的结果集——一个简单的文档列表,快速匹配运算并存入内存是十分方便的,每个文档仅需要1个字节。这些缓存的过滤结果集与后续请求的结合使用是非常高效的。
查询语句不仅要找出相匹配的文档,还需要计算每个文档的相关性,所以一般来说查询语句要比过滤语句更耗时,并且查询结果也不可缓存。
幸亏有了倒排索引,一个只匹配少量文档的简单查询语句在百万级文档中的查询效率会与一条经过缓存的过滤语句旗鼓相当,甚至略占上风。但是一般情况下,一条经过缓存的过滤查询要远胜一条查询语句的执行效率。
过滤语句的目的就是缩小匹配的文档结果集,所以需要仔细检查过滤条件。
原则上来说,使用查询语句做全文本搜索或其他需要进行相关性评分的时候,剩下的全部用过滤语句。
Elasticsearch提供了丰富的查询过滤语句,而有一些是我们较常用到的。现在我们快速的介绍一下这些最常用到的查询过滤语句。
term主要用于精确匹配哪些值,比如数字,日期布尔值或not_analyzed的字符串(未经分析的文本数据类型):
{ "term" : { "age" : 26 } }
{ "term" : { "date" : "2014-09-01" } }
{ "term" : { "public" : true } }
{ "term" : { "tag" : "full_text" } }
terms跟term有点类似,但terms允许指定多个匹配条件。如果某个字段指定了多个值,那么文档需要一起去做匹配:
{
"terms" : {
"tag" : { "search" , "full_text" , "nosql" }
}
}
range过滤允许我们按照指定范围查找一批数据:
{
"range" : {
"age" : {
"gte" : 20,
"lt" :30
}
}
}
范围操作符包含:
gt → 大于 gte → 大于等于
lt → 小于 lte → 小于等于
exists和missing过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件
{
"exists" : {
"field" : "title"
}
}
这两个过滤只是针对已经查询一批数据来,但是想区分出某个字段是否存在的时候使用。
bool过滤可以用来合并多个过滤条件查询结果布尔逻辑,它包括一个操作符:
must 多个查询条件的完全匹配,相当于and。
must_not 多个查询条件的相反匹配,相当于not。
should 至少有一个查询条件匹配,相当于or。
这些参数可以分别继承一个过滤条件或者一个过滤条件的数组:
{
"bool" : {
"must" : { "term" : { "folder" : "inbox" } } ,
"must_not" : { "term" : { "folder" : "index" } },
"should" : [
{ "term" : { "term" : { "starred" : true } } },
{ "term" : { "term" : { "unread" : true } } }
]
}
}
使用match_all可以查询到所有文档,是没有查询条件下的默认语句。
{
"match_all" : {}
}
此查询常用于合并过滤条件。比如说你需要检索所有的邮箱,所有的文档相关性都是相同的,所以得到的_source为1
match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。
如果你使用match查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:
{
"match" : {
"tweet" : "About Search"
}
}
如果你使用match查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:
{
"match" : {
"tweet" : "About Search"
}
}
如果用match下制定了一个确切值,在遇到数字、日期、布尔值或者not_analyzed的字符串时,它将为你搜索你给定的值:
{ "match" : { "age" : 26 } }
{ "match" : { "date" : "2014-09-01" } }
{ "match" : { "public" : true } }
{ "match" : { "tag" : "full_text" } }
提示:做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。
不像我们以前介绍的字符查询,match查询不可以用类似“+usid:2+tweet:search”这样的语句。它只能就指定某个确切字段某个确切的值进行搜索,而你要做的就是为它指定正确的字段名以避免语法错误。
multi_match查询允许你做match查询的基础上同时搜索多个字段:
{
"multi_match" : {
"query" : "full text search",
"fields" : [ "title" , "body" ]
}
}
bool查询与bool过滤相似,用于合并多个查询子句。不同的是,bool过滤可以直接给出是否匹配成功,而bool查询要计算每一个查询子句的_source(相关性分值)。
must:查询指定文档一定要被包含。
must_not:查询指定文档一定不要被包含。
should:查询指定文档,有则可以为文档相关性加分。
以下查询将会找到title字段中包含“how to make millions”,并且“tag”字段没有被标识为span。如果有标识为“starred”或者发布日期为2014年之前,那么这些匹配的文档将比同类网站等级高:
{
"bool" : {
"must" : { "match" : { "title" : "how to make milliions" } },
"must_not" : { "match" : {"tag" : "span"} },
"should" : {
{ "match" : { "tag" : "starred" } },
{ "range" : { "date" : { "gte" : "2014-01-01" } } }
}
}
}
提示:如果bool查询下没有must子句,那至少应该有一个should子句。但是如果有must子句,那么没有should子句也可以进行查询。
查询语句和过滤语句可以放在各自的上下文中。在Elasticsearch API中我们会看到许多带有query或filter的语句。这些语句既可以包含单条query语句,也可以包含一条filter子句。换句话说,这些语句需要首先创建一个query或filter的上下文关系。
复合查询语句可以加入其它查询子句,复合过滤子句也可以加入其它过滤子句。通常情况下,一条查询语句需要过滤语句的辅助,全文本搜索除外。
所以说,查询语句可以包含过滤子句,反之亦然,以便于我们切换query或filter的上下文。这就要求我们在读懂需求的同事构造正确有效的语句。
比如说我们有这样一条查询语句:
{
"match" : {
"email" : "business opportunity"
}
}
然后我们想要让这条语句加入term过滤,在收信箱中匹配邮件:
{
"term" : {
"folder" : "inbox"
}
}
search API 中只能包含query语句,所以我们需要用filtered来同时包含“query”和“filter”子句:
{
"filtered" : {
"query" : { "match" : { "email" : "business opportunity" } },
"filter" : { "term" : { "folder" : "inbox" } }
}
}
在query上下文中,如果你需要一条过滤语句,比如在匹配全部邮件的时候,你可以省略query子句:
GET /_search
{
"query" : {
"filtered" : {
"filter" : { "term" : { "folder" : "inbox" } }
}
}
}
如果一条查询语句没有指定查询范围,那么它默认使用match_all查询,所以上面语句的完整形式如下:
GET /_search
{
"query" : {
"filtered" : {
"query" : { "match_all" : {} },
"filter" : { "term" : { "folder" : "inbox" } }
}
}
}
有时候,你需要在filter的上下文中使用一个query子句。下面的语句就是一条带有查询功能的过滤语句,这条语句可以过滤掉看起来像垃圾邮件的文档:
GET /_search {
"query" : {
"filtered" : {
"bool" : {
"must" : { "term" : { "folder" : "inbox" } },
"must_not" : {
"query" : { <1>
"match" : { "email" : "urgent business proposal" }
}
}
}
}
}
}
<1> 过滤语句中可以使用query查询的方式代替bool过滤子句。
提示:我们很少用到的过滤语句中包含查询,保留这种用法只是为了语法的完整性。只有在过滤中用到全文本匹配的时候才会使用这种结构。
查询语句可以变得非常复杂,特别是与不同的分析器和字段映射相结合后,就会有些难度。
validate API可以验证一条查询语句是否合法。
GET /gb/tweet/_validate/query
{
"query" : {
"tweet" : {
"match" : "really powerful"
}
}
}
以上请求的返回值告诉我们这条语句是非法的:
想知道语句非法的具体错误信息,需要加上explain参数:
GET /gb/tweet/_validate/query?explain <1>
{
"query" : {
"tweet" : {
"match" : "really powerful"
}
}
}
<1> explain 参数可以提供语句错误的更多详情。
很显然,我们把query语句的match与字段名位置弄反了:
如果是合法语句的话,使用explain参数可以返回一个带有查询语句的可阅读描述,可以帮助了解查询语句在ES中是如何执行的:
GET /_validate/query?explain
{
"query" : {
"match" : {
"tweet" : "really powerful"
}
}
}
explanation会为每一个索引返回一段描述,因为每个索引会有不同的映射关系和分析器:
从返回的explanation你会看到match是如何为查询字符串“really powerful”进行查询的,首先,它被拆分成两个独立的词分别在tweet字段中进行查询。
而且,在索引us中这两个词为“really” 和 “powerful”,在索引gb中被拆分成“really”和“power”。这是因为我们在索引gb中使用了english分析器。
因篇幅问题不能全部显示,请点此查看更多更全内容