ElasticSearch 5学习(10)——结构化查询(包括新特性)

之前我们所有的查询都属于命令行查询,但是不利于复杂的查询,而且一般在项目开发中不使用命令行查询方式,只有在调试测试时使用简单命令行查询,但是,如果想要善用搜索,我们必须使用请求体查询(request body search)API。之所以这么称呼,是因为大多数的参数以JSON格式所容纳而非查询字符串。请求体查询,并不仅仅用来处理查询,而且还可以高亮返回结果中的片段,并且给出帮助你的用户找寻最好结果的相关数据建议。

空查询

我们以最简单的search API开始,空查询将会返回索引中所有的文档。

GET /_search
{}

同字符串查询一样,可以查询一个,多个或_all索引(indices)或类型(types):

也可以使用from及size参数进行分页:

GET /_search
{
  "from": 30,
  "size": 10
}

需要注意的是:携带内容的GET请求?

任何一种语言(特别是js)的HTTP库都不允许GET请求中携带交互数据。 事实上,有些用户很惊讶GET请求中居然会允许携带交互数据。

真实情况是,http://tools.ietf.org/html/rfc7231#page-24[RFC 7231], 一份规定HTTP语义及内容的RFC中并未规定GET请求中允许携带交互数据! 所以,有些HTTP服务允许这种行为,而另一些(特别是缓存代理),则不允许这种行为。

Elasticsearch的作者们倾向于使用GET提交查询请求,因为他们觉得这个词相比POST来说,能更好的描述这种行为。然而,因为携带交互数据的GET请求并不被广泛支持,所以searchAPI同样支持POST请求,类似于这样:

POST /_search
{
  "from": 30,
  "size": 10
}

这个原理同样应用于其他携带交互数据的GET API请求中。

结构化查询 Query DSL

结构化查询是一种灵活的,多表现形式的查询语言。 Elasticsearch在一个简单的JSON接口中用结构化查询来展现Lucene绝大多数能力。 你应当在你的产品中采用这种方式进行查询。它使得你的查询更加灵活,精准,易于阅读并且易于debug。

使用结构化查询,你需要传递query参数:

GET /_search
{
    "query": 发查询体放置于此即可
}

空查询 - {} - 在功能上等同于使用match_all查询子句,正如其名字一样,匹配所有的文档:

GET /_search
{
    "query": {
        "match_all": {}  #查询体
    }
}

查询子句

一个查询子句一般使用这种结构:

#整个属于查询体
{
    QUERY_NAME(查询命令): {
        ARGUMENT: VALUE,
        ARGUMENT: VALUE,...
    }
}

或指向一个指定的字段:

#整个属于查询体
{
    QUERY_NAME(查询命令): {
        FIELD_NAME(匹配字段): {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
}

例如,你可以使用match查询子句用来找寻在tweet字段中找寻包含,elasticsearch的成员:

{
    "match"(查询命令): {
        "tweet": "elasticsearch"
    }
}

完整的查询请求会是这样:

GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    }
}

合并多子句

查询子句就像是搭积木一样,可以合并简单的子句为一个复杂的查询语句,比如:

  • 叶子子句(leaf clauses)(比如match子句)用以在将查询字符串与一个字段(或多字段)进行比较
  • 复合子句(compound)用以合并其他的子句。例如,bool子句允许你合并其他的合法子句,mustmust_not或者should,如果可能的话:
{
    "bool": {
        "must":     { "match": { "tweet": "elasticsearch" }},
        "must_not": { "match": { "name":  "mary" }},
        "should":   { "match": { "tweet": "full text" }}
    }
}

复合子句能合并任意其他查询子句,包括其他的复合子句。这就意味着复合子句可以相互嵌套,从而实现非常复杂的逻辑。

以下实例查询的是邮件正文中含有“business opportunity”字样的星标邮件或收件箱中正文中含有“business opportunity”字样的非垃圾邮件:

#整个属于查询体
{
    "bool": {
        "must": { "match":      { "email": "business opportunity" }},
        "should": [
             { "match":         { "starred": true }},
             { "bool": {
                   "must":      { "folder": "inbox" },
                   "must_not":  { "spam": true }
             }}
        ],
        "minimum_should_match": 1
    }
}

查询与过滤

Elasticsearch使用的DSL具有一组称为查询的组件,它们可以混合并以无穷组合进行匹配。这一组组件可以在两个上下文中使用:过滤上下文和查询上下文。

当用于过滤上下文时,该查询被称为“非评分”或“过滤”查询。也就是说,查询只询问问题:“此文档是否匹配?”。答案总是一个简单的二进制yes|no。

  • created的日期范围是否在 2013 到 2014 ?
  • status字段中是否包含单词 "published" ?
  • lat_lon字段中的地理位置与目标点相距是否不超过10km ?

当在查询上下文中使用时,查询变为“评分”查询。类似于其非评分兄弟,这确定文档是否匹配以及文档匹配的程度。

查询的典型用法:

  • 查找与 full text search 这个词语最佳匹配的文档
  • 查找包含单词 run ,但是也包含runs, running, jogsprint的文档
  • 同时包含着 quick, brownfox --- 单词间离得越近,该文档的相关性越高
  • 标识着 lucene, searchjava --- 标识词越多,该文档的相关性越高

评分查询计算每个文档与查询的相关程度,并为其分配相关性_score,稍后用于按相关性对匹配文档进行排序。这种相关性的概念非常适合于全文搜索,其中很少有完全“正确”的答案。

新特性:历史上,查询和过滤器是Elasticsearch中的单独组件。从Elasticsearch 2.0开始,过滤器在技术上被消除,并且所有查询都获得了成为非评分的能力。

然而,为了清楚和简单,将使用term“过滤器”来表示在非评分过滤上下文中使用的查询。可以将term“过滤器”,“过滤查询”和“非评分查询”视为相同。
类似地,如果单独使用term“查询”而不使用限定符,指的是“评分查询”。

关于具体的Query DSL变化可以查看Query DSL changes

性能差异

过滤查询是对集合包含/排除的简单检查,这使得计算非常快。当您的过滤查询中至少有一个是“稀疏”(匹配文档较少)时,可以利用各种优化,并且可以将经常使用的非评分查询缓存在内存中以便更快地访问。

相比之下,评分查询不仅必须找到匹配的文档,而且还要计算每个文档的相关程度,这通常使得他们比他们的非评分对手更重。此外,查询结果不可缓存。

由于倒排索引,只匹配几个文档的简单评分查询可能与跨越数百万个文档的过滤器一样好或更好。然而,一般来说,过滤器将胜过评分查询。

过滤的目的是减少必须由评分查询检查的文档的数量。

什么情况下使用

作为一般规则,对全文搜索或任何会影响相关性分数的条件使用查询子句,并对其他所有条件使用过滤器。

最重要的查询过滤语句

match_all 查询

match_all查询只匹配所有文档。如果未指定任何查询,则是使用的默认查询:

{“match_all”:{}}

此查询经常与过滤器结合使用,例如,用于检索收件箱文件夹中的所有电子邮件。所有文件被认为是同等相关的,所以他们都获得1的中性分数。

match 查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

如果你使用match查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:

{
    "match": {
        "tweet": "About Search"
    }
}

如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed的字符串时,它将为你搜索你给定的值:

{ "match": { "age":    26           }}
{ "match": { "date":   "2014-09-01" }}
{ "match": { "public": true         }}
{ "match": { "tag":    "full_text"  }}

提示: 做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。

不像我们在ElasticSearch 5学习(4)——简单搜索笔记中介绍的字符查询,match查询不可以用类似"+usid: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用于按照精确值进行搜索,无论是数字,日期,布尔值还是未分析的精确值字符串字段:

{ "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查询类似,不对输入文本执行分析。它正在寻找精确匹配(包括大小写,重音,空格等)。

exists and missing 查询

existsmissing查询用于查找指定字段具有一个或多个值(exists)或没有任何值(missing)的文档。
它在本质上类似于SQL中的IS_NULL(缺失)和NOT IS_NULL(存在):

{
    "exists":   {
        "field":    "title"
    }
}

这些查询经常用于仅在存在字段时应用条件,以及在缺少条件时应用不同的条件。

查询与过滤条件的合并

现实世界的搜索请求从来不简单;他们使用各种输入文本搜索多个字段,并根据条件数组进行过滤。要构建复杂的搜索,您需要一种将多个查询组合到一个搜索请求中的方法。

要做到这一点,你可以使用bool询。此查询在用户定义的布尔组合中将多个查询组合在一起。此查询接受以下参数:

bool 过滤

bool 过滤可以用来合并多个过滤条件查询结果的布尔逻辑,它包含一下操作符:

must :: 多个查询条件的完全匹配,相当于 and。

must_not :: 多个查询条件的相反匹配,相当于 not。

should :: 至少有一个查询条件匹配, 相当于 or。

这些参数可以分别继承一个过滤条件或者一个过滤条件的数组:

{
    "bool": {
        "must":     { "term": { "folder": "inbox" }},
        "must_not": { "term": { "tag":    "spam"  }},
        "should": [
                    { "term": { "starred": true   }},
                    { "term": { "unread":  true   }}
        ]
    }
}

因为这是我们见过的第一个包含其他查询的查询,所以我们需要谈论分数是如何组合的。每个子查询子句将单独计算文档的相关性分数。一旦计算了这些分数,bool查询将将分数合并在一起,并返回表示布尔运算的总分数的单个分数。

以下查询将会找到 title 字段中包含 "how to make millions",并且 tag 字段没有被标为 "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" }}}
        ]
    }
}

提示: 如果bool查询下没有must子句,那至少应该有一个should子句。但是如果有must子句,那么没有should子句也可以进行查询。

添加过滤查询

如果我们不希望文档的日期影响评分,我们可以重新排列前面的示例以使用过滤子句:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }}
        ],
        "filter": {
          "range": { "date": { "gte": "2014-01-01" }} 
        }
    }
}

范围查询已从should子句中移出并进入过滤器子句。

通过将范围查询移动到过滤子句中,我们将其转换为非评分查询。它将不再为文档的相关性排名贡献分数。并且因为它现在是一个非评分查询,它可以使用可用于过滤器的各种优化,这应该提高性能。

任何查询都可以以这种方式使用。只需将查询移动到bool查询的过滤器子句中,它就会自动转换为非评分过滤器。

如果你需要过滤许多不同的标准,bool查询本身可以用作非评分查询。只需将它放在过滤器子句中,并继续构建布尔逻辑:

{
    "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" }}
              ]
          }
        }
    }
}

通过在filter子句中嵌入bool查询,我们可以为我们的过滤条件添加布尔逻辑。

constant_score 查询

尽管不像bool查询那样经常使用,但是constant_score查询在你的工具箱中仍然有用。查询对所有匹配的文档应用静态,常数得分。它主要用于当你想执行一个过滤器,没有别的(例如没有评分查询)。你可以使用它而不是一个只有过滤器子句的bool。性能将是相同的,但它可以帮助查询简单/清晰。

{
    "constant_score":   {
        "filter": {
            "term": { "category": "ebooks" } 
        }
    }
}

验证查询

查询语句可以变得非常复杂,特别是与不同的分析器和字段映射相结合后,就会有些难度。

validate API 可以验证一条查询语句是否合法。

GET /gb/tweet/_validate/query
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}

以上请求的返回值告诉我们这条语句是非法的:

{
  "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如何解释查询是有用的:

GET /gb/tweet/_validate/query?explain
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}

为每个我们查询的索引返回一个解释,因为每个索引可以有不同的映射和分析器:

{
  "valid" :         true,
  "_shards" :       { ... },
  "explanations" : [ {
    "index" :       "us",
    "valid" :       true,
    "explanation" : "tweet:really tweet:powerful"
  }, {
    "index" :       "gb",
    "valid" :       true,
    "explanation" : "tweet:realli tweet:power"
  } ]
}

从解释中,您可以看到查询字符串的match查询really powerful已被重写为对tweet字段的两个单项查询,每个term一个。

此外,对于我们的索引,这两个termreallypowerful的,而对于gb索引,termreallipower。原因是我们改变了gb索引中的tweet字段以使用english分析器。

转载请注明出处。
作者:wuxiwei
出处:http://www.cnblogs.com/wxw16/p/6204644.html

posted @ 2016-12-20 22:38  wuxiwei  阅读(4206)  评论(2编辑  收藏  举报