ElasticSearch(七)--请求体查询

简单查询lite search (字符串查询)是一种有效的命令行ad hoc 查询,但是想要善用搜索,必须使用请求体查询request  body search API.之所以这么称呼,是因为大多数的参数以JSON格式所容纳,而不是查询字符串.

请求体查询不但可以处理查询,而且还可以高亮返回结果中的片段.

1.空查询

GET _search
{}
同字符串查询一样,你可以查询一个,或多个索引及类型

GET /index_2014*/type1,type2/_search
{}
也可以使用from, size参数进行分页pagination:

GET /website/_search
{
  "from":1,
  "size":3
}
注意,from和size数值均可以与实际不符,返回的不过是个空的数组,并不会出错.

那么,这中请求体查询,使用的是携带内容的GET请求方式?

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

但是真实情况是,一份关于HTTP协议的标准文档RFC中并未定义一个GET请求携带请求体会发生什么!所以,

ES的作者们倾向于使用GET提交查询请求,因为它们觉得这个词相比于POST能更好的描述这种行为.然而,因为携带请求体的GET请求并不被广泛支持,所以search API同样支持POST请求.

POST /website/_search
{
  "from":1,
  "size":3
}
相比于神秘的字符串查询方式,请求体查询允许我们通过使用query DSL(Domian Specific Language)来写入参数.

2. Query DSL

query DSL是一种灵活的,表现力强的查询语言,ES通过一个简单的JSON接口使用DSL来表现lucene绝大多数的能力.

应当在你的产品中使用这种方式进行查询,它是你的查询更加灵活,精准,易于阅读,且易于debug.

为了使用query DSL,传递一个查询给query参数:

GET /_search
{
    "query": YOUR_QUERY_HERE
}
例如,空查询,其实就相当于使用了一个match_all查询子句

POST /website/_search
{
  "query": {
    "match_all": {}
  }
}
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"
        }
    }
}
称之为查询子句query clause,代表其都是放到query语句下的.

合并多子句

查询子句像一个简单的积木块一样,可以和其他的子句组合,构成复杂的查询.

子句可以分为:

叶子子句leaf clause,被用作字符串与字段的比较.

复合子句compound clause,备用做合并其他的子句.例如一个bool子句,允许合并其他的子句:must 匹配, must_not,should.它还允许包含non-scoring, filters作为结构化搜索:

{
    "bool": {
        "must":     { "match": { "tweet": "elasticsearch" }},
        "must_not": { "match": { "name":  "mary" }},
        "should":   { "match": { "tweet": "full text" }},
        "filter":   { "range": { "age" : { "gt" : 30 }} }
    }
}
非常重要的指出,一个复合查询子句可以包含其他任何查询子句,或者别的复合子句.这意味着复合子句可以被相互嵌套,允许复杂的逻辑表达.

例如,下边的例子,查询邮件,满足:包含business opportunity,同时被标星的邮件;或者同时在folder,indbox,但是没有被标记为spam的邮件.

{
    "bool": {
        "must": { "match":   { "email": "business opportunity" }},
        "should": [
            { "match":       { "starred": true }},
            { "bool": {
                "must":      { "match": { "folder": "inbox" }},
                "must_not":  { "match": { "spam": true }}
            }}
        ],
        "minimum_should_match": 1
    }
}
不要担心这些例子的细节,我们后续会解释.重点是明白复合语句可以组合多个子句,包括叶子子句或这复合子句到一个简单的查询中.

3. 查询和过滤

ES使用DSL将查询子句放到一个简单的集里,这种简单集合可以被用作两种环境:过滤上下文Filtering context和查询上下文query context

当被用到过滤环境中,查询query被称作non-scoring or filtering query,这样的查询会这样问问题,'这个文档是否匹配?'答案是二选一,是或否.

例如;

created 的日期范围是否介于2013-2014?

status字段是否包含词published?

las_lon字段的地理位置是否与目标相距不超过10km?

当被使用在查询环境中,查询成为scoring query,它这样问"这个文档的匹配程度如何?"

查询典型的使用:

查找与full text search 最佳匹配的文档

包含单词run,也可能是running,runs,jog, sprint

同时包含quick , brown, fox,它们离得越近,文档的匹配相关性越高.

标记着lucene, search, java,标识词越多,文档的相关性越高.

一个scoring query,计算文档与查询的相关性,并赋值给字段_score,用作依据相关性排序的标准.这种概念同样适用于全文搜索.

注意:

历史上,在ES中,查询和过滤是分开做的,在ES2.0开始,过滤被技术性的消除,同时,查询开始支持non-scoring式的查询.

然而为了区分和简便,我们仍用"过滤"一词来描述non-socring的查询.你可以把filter , filter query , non-scoring query当作一样的.

同样的,如果查询一词被单独的使用,我们就认为是scoring的查询.

性能差异

过滤查询是一个简单的包含与不包含的检查,这是它们计算非常快速.

有各种优化,对于至少有一条过滤查询是很少有文档匹配,同时被频繁的用作non-scoring的查询,可以被放到内存中,更快速获取.

相比之下,scoring查询不但需要查找匹配的文档,并且还要计算相关性,这使得其繁重于non-scoring查询,同时查询的结果是不能够被缓存的.

幸亏有倒排索引,使得一个简单的scoring查询,仅匹配一些文档,性能可以与过滤相比,甚至优于过滤,在跨越数以百万计的文件中.

但是一般情况下,过滤是优于查询的.

过滤的目的是减少文档的数量,这些文档必须被scoring query检查.

什么时候使用?

一般原则,在全文查询,或者需要相关性评分时,使用查询scoring query,其他时候都是使用过滤non-scoring query.

4. 重要的查询语句

ES有很多查询语句,只有少部分经常被使用,我们会在后续的深入查询一章详细学习,现在快速介绍一些重要的语句.

match_all

match_all查询简单的匹配所有文档

{ "match_all": {}}
这个查询经常和过滤器一起使用.

match

match查询是一个标准的查询,无论是查询一个全文文本还是精确值.

如果使用match对全文文本字段进行查询,执行查询之前,先使用针对该字段正确的分析器对查询字符串进行分析.

{ "match": { "tweet": "About Search" }}
如果使用该语句在一个字段上匹配精确值,数值,日期,布尔,以及not_analyzed字符串,

{ "match": { "age":    26           }}
{ "match": { "date":   "2014-09-01" }}
{ "match": { "public": true         }}
{ "match": { "tag":    "full_text"  }}
对于确切值搜索,你可能想使用过滤语句,而不是查询,我们很快看到过滤的例子.

相比于字符串查询,match语句查询的语法更加安全.

multi_match

multi_match查询允许在多个字段上进行match一样的查询

{
    "multi_match": {
        "query":    "full text search",
        "fields":   [ "title", "body" ]
    }
}
range
range查询允许查询数值或日期在一个指定的区间里,该子句接受如下参数:

gt : greater than

gte : greater than or equal to

lt : less than

lte : less than or equal to

{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}
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" ] }}
exist, missing

exist, missing查询被用作查询指定的字段存在的文档(exist)或者不存在的文档(missing),exist返回存在该字段的文档,missing返回不存在该字段的文档

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

5. 组合查询

现实应用中的查询从来都不是简单的,使用多个输入值查询多个字段,依据一系列标准的过滤器.构造一个复杂查询,你需要一种组合多个查询子句在一个搜索请求中的方式.

为了达到这个要求,可以使用bool查询,这个查询接受如下参数:

must: 必须是匹配的文档被包含进来

must_not: 一定是不匹配的文档被包含进来

should: 如果匹配,增加_score,否则没有影响,为每个文档相关性评分.

filter: 必须匹配,是non_scoring的过滤模式,只是简单的包含或不包含.

因为这是我们看到的第一个包含其他查询的查询语句,我们需要谈论相关性评分是怎么计算的.

每个子句分别计算文档的相关性评分,一旦这些结果被计算出来,bool语句将这些分数合并到一起,并且返回一个单个分数值,代表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" }}}
        ]
    }
}
加上过滤查询:

如果我们不想文档的日期对评分产生影响,我们可以使用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查询放入filter子句,我们转化它为non-scoring查询,它不再对文档的相关性评分产生影响,并且因为是non-scoring查询,可以使用过滤器的优化来提升性能.

任何一个查询都可以使用这种方式,简单的将查询放到bool语句的filter子句中,会自动转化为non-scoring过滤.

如果需要一个基于多标准的过滤,bool查询本身可以作为non-scoring查询

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

尽管不如bool查询经常使用,constant_score查询也依然是有用的,该查询为匹配的文档应用静态的,常数的分数.它主要是在执行过滤查询时使用.

只有过滤子句时,你可以使用该语句代替bool语句.性能是相同的,但是有利于查询的简单性和清晰度

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

6. 验证查询

查询可以是非常复杂,尤其是组合了不同的分析器和字段映射的时候,validate-query API可以检查一个请求是否有效.

在请求URL后加/_validate/query

GET /gb/tweet/_validate/query
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}
validate请求的响应告诉我们请求是无效的:

{
  "valid" :         false,
  "_shards" : {
    "total" :       1,
    "successful" :  1,
    "failed" :      0
  }
}
如果要知道问题处在了哪,可以在后边加上参数explain

GET /gb/tweet/_validate/query?explain 
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}
显然,我们混淆了查询语句的类别和字段的名字

{
  "valid" :     false,
  "_shards" :   { ... },
  "explanations" : [ {
    "index" :   "gb",
    "valid" :   false,
    "error" :   "org.elasticsearch.index.query.QueryParsingException:
                 [gb] No query registered for [tweet]"
  } ]
}
我们也可以利于expalin参数理解ES是如何解释查询的:

GET /us,gb/_validate/query?explain
{
  "query": {
    "match": {
      "tweet": "really powerful"
    }
  }
}
为我们查询的每个索引返回一个explanation,因为每个索引有不同的映射和分析器:

{
  "valid": true,
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "explanations": [
    {
      "index": "gb",
      "valid": true,
      "explanation": "tweet:realli tweet:power"
    },
    {
      "index": "us",
      "valid": true,
      "explanation": "tweet:really tweet:powerful"
    }
  ]
}
从explanation中,我们可以看出针对tweet字段,match语句是如何将查询字符串really powerful 重写为两个单个词term的.

两个索引的重写词不一样,原因是因为索引gb中的tweet字段使用的是english分析器.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值