ES3_4请求体查询

请求体查询(查询表达式)包含了ES大部分查询功能。

es 倾向于使用 GET 来表达查询操作,查询条件可以放到请求体中,而事实是这个 RFC 文档 RFC 7231 — 一个专门负责处理 HTTP 语义和内容的文档 — 并没有规定一个带有请求体的 GET 请求应该如何处理!结果是,一些 HTTP 服务器允许这样子,而有一些 — 特别是一些用于缓存和代理的服务器 — 则不允许。所以 es 作了一个妥协,下面的 GET 请求同样可以使用 POST 来实现:

1
2
3
4
5
6
7
8
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"last_name" : "Smith"
}
}
}

查询表达式主要有两种结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
{
QUERY_NAME: {
FIELD_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
}

验证查询是否合法

1
2
3
4
GET /_validate/query?explain
{
"query" : { ... }
}

精确匹配 - term

  • term:精确值 匹配,这些精确值可能是数字、时间、布尔或者那些 not_analyzed 的字符串,注意term 不会对输入的文本进行分析,这样和索引过的目标字符串比较很有可能出现明明看起来一样却匹配不上的现象(比如”Jane”被分析后变成”jane”,term 查询时使用”Jane”进行精确匹配当然匹配不上了!);
  • terms:同样是精确值匹配,但可以指定一个数组进行匹配;
    1
    2
    { "term" : {"first_name": "jane"}}
    { "terms": { "tag": [ "search", "full_text", "nosql" ] }}
  • keyword严格匹配
    keyword类型字段可以精确匹配:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    GET /products/_search
    {
    "query": {
    "term": {
    "productId.keyword": {
    "value": "123"
    }
    }
    }
    }

全文查询 - match

全文查询的特点:

  • 索引和搜索时都会进行分词,查询字符串先传递到一个合适的分词器,然后生成一个供查询的词项列表;
  • 查询的时候,先会对输入的查询进行分词,然后每个词项逐个进行底层的查询,最终将结果进行合并,并为每个文档生成一个算分。
    比如查询”Hello World“,会查到包括”Hello“或”World“的所有结果。

match

如果你在一个全文字段上使用 match 查询,在执行查询前,它将用正确的分析器去分析查询字符串;
如果在一个精确值的字段上使用它, 例如数字、日期、布尔或者一个 not_analyzed 字符串字段,那么它将会精确匹配给定的值;

1
2
3
4
5
6
7
8
9
PUT test/_search
{
"profile": "true",
"query": {
"match": {
"content": "Hello World"
}
}
}

match会对查询条件进行分词,然后拿分词结果去索引中匹配,多个词的组合是OR关系,比如上面的”Hello World”,会分成”hello”和”world”,且字段只要包含其中之一就返回。
可以通过operator来指定组合逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
PUT test/_search
{
"profile": "true",
"query": {
"match": {
"content": {
"query": "Hello World",
"operator": "AND" // 必须同时包含hello和world
}
}
}
}

需要注意的是,如果使用match方法查询keyword字段,查询会被自动转成term,而term查询不会做分词处理:

1
2
3
4
5
6
7
8
9
PUT test/_search
{
"profile": "true",
"query": {
"match": {
"content.keyword": "hello world"
}
}
}

match_phrase

短语匹配,同样会先解析查询字符串来产生一个词条列表,但只返回包含所有搜索词条的文档,且词条的位置要邻接。
当然精确匹配要求太过严格了,一般可以加slop来引入一些灵活性,即相隔多远时仍视为匹配:

1
2
3
4
5
6
7
8
9
10
11
12
PUT test/_search
{
"profile": "true",
"query": {
"match_phrase": {
"content": {
"query": "Hello World",
"slop": 1
}
}
}
}

match_all

简单的匹配所有文档,往往和 filter 搭配;

单字符串多字段查询 - multi_match

常见的场景:

  • 最佳匹配(Best Fields)
    当字段之间相互竞争,又相互关联。例如title和content字段,最终评分来自最匹配字段。
  • 多数字段(Most Fields)
    一个用来调优相关度的常用技术是将相同的数据(也可以包含拼音、同义词等)索引到多个字段中。它用来尽可能多地匹配文档。
  • 混合字段(Cross Field)
    对于某些实体需要在多个字段中确定信息,比如要确定一个人需要同时搜索first_name和last_name字段,确定一本书需要同时搜索title、author和description。
1
2
3
4
5
6
7
8
9
10
11
12
GET doc/_search
{
"query": {
"multi_match": {
"type": "best_fields",
"query": "a",
"fields": ["title", "content"],
"tie_breaker": 0.2,
"minimum_should_match": "20%"
}
}
}
  • best_fields:类似dis_max
  • most_fields:ES会为每个字段生成一个match查询,然后将它们包含在一个bool查询中。
  • cross_fields:首先分析查询字符串并生成一个词列表,然后从所有字段中依次搜索每个词,然后取最小的的IDF作为最终的IDF

Query String

查询字符串,类似查询脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST blogs/_search
{
"query": {
"query_string": {
"default_field": "content",
"query": "Elasticsearch AND rocks"
}
}
}
POST blogs/_search
{
"query": {
"query_string": {
"default_field": "content",
"query": "(Elasticsearch AND rocks) OR (Java)"
}
}
}

过滤 - constant_score、filter

constant_score查询可以将Query转换成Filter,不再计算相关性,且Filter可以有效利用缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /products/_search
{
"explain": true,
"query": {
"constant_score": {
"filter": {
"term": {
"productId.keyword": "123"
}
}
}
}
}

将一个不变的常量评分(即 1)应用于所有匹配的文档。经常用于你只需要执行一个 filter 而没有其它查询的情况下,如果使用 bool 查询_score 值将总是为 0:

1
2
3
4
5
6
7
{
"constant_score": {
"filter": {
"term": { "category": "ebooks" }
}
}
}

filter:不评分查询,实际上是给了每个查询出的文档一个默认的中性评价 1,并且过滤结果将会被缓存

复合查询及算分控制 - bool

bool复合语句,可以组合其他语句:

  • must 必须匹配,贡献算分
  • must_not 必须不匹配
  • should 如果满足则增加 _score,否则无影响
  • filter 用来排除文档,不评分

每一个子查询都独自地计算文档的相关性得分,一旦他们的得分被计算出来, bool 查询就将这些得分进行合并并且返回一个代表整个布尔操作的得分。(虽说是布尔操作,但是结果分数其实是[0, 1]范围内的小数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"query": {
"bool": {
"must": { "match": { "title": "how to make millions" }},
"must_not": { "match": { "tag": "spam" }},
"should": [
{ "match": { "tag": "starred" }}
],
"filter": {
"range": { "date": { "gte": "2014-01-01" }}
}
}
}
}

语法如上所示:

  • 子查询(指must这些)可以以任意顺序出现
  • 可以嵌套多个查询
  • 如果你的bool查询中,如果没有 must 语句,那么至少需要能够匹配其中的一条 should 语句;如果存在至少一条 must 语句,则对 should 语句的匹配没有要求。

算分过程

should语句算分:

  • 查询should语句中的两个查询
  • 对算分加和
  • 乘以匹配语句的总数
  • 除以所有语句的总数

disjunction max query

比如查询一篇文档,我们用两个条件分别查询title和body字段,两个查询的算分是竞争的。
实际上应该找到单个最佳匹配的字段的评分,比如body中查询算分更高的话,应该采用body的算分结果。
在ES中可以采用dis_max来实现,查询结果采用字段上最匹配的评分作为最终评分返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PUT /doc/doc/3
{
"title": "asdsadaaadsa a a dwedewd",
"content": "asdewr a asdgreger"
}

GET /doc/_search
{
"query": {
"dis_max": {
"queries": [
{"match": { "title": "a" }},
{"match": { "content": "a" }}
]
}
}
}
  • 因为title中a出现了2次,因此评分会更高,最后的评分结果也是返回的title的算分
  • 和下面的查询算分是相同的:
    1
    2
    3
    4
    5
    6
    7
    8
    GET /doc/_search
    {
    "query": {
    "match": {
    "title": "a"
    }
    }
    }

最佳匹配和次佳匹配的算分是可以用系数调整的,如下面的tie_breaker参数:

1
2
3
4
5
6
7
8
9
10
11
12
GET /doc/_search
{
"query": {
"dis_max": {
"queries": [
{"match": { "title": "a" }},
{"match": { "content": "a" }}
],
"tie_breaker": 0.2
}
}
}

tie_breaker是一个介于0-1之间的浮点数,0代表使用最佳匹配最为最终评分,1代表所有语句同等重要,其计算过程如下所示:

  1. 获取最佳匹配语句的评分_score
  2. 将其他匹配语句的评分与tie_breaker相乘
  3. 对以上评分求和并规范化

嵌套bool

比如下面的语句实现了should not的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
GET /doc/doc/_search
{
"query": {
"bool": {
"must": [
{"term": { "tag": "a" }}
],
"should": [
{
"bool": {
"must_not": [
"term": {
"avaliable": "false"
},
"term": {
"count": "1"
}
]
}
}
],
"minimum_should_match": 1
}
}
}
  • 同一层级下的竞争字段,具有相同的权重
  • 通过嵌套bool查询,可以改变对算分的影响
    比如上面should子句中的两个条件合起来贡献的算分才和must相同。

多值count

在ES中所有字段都是多值的,普通查询都是包含关系,比如下面查询tag字段包含”a”这项的文档:

1
2
3
4
5
6
7
8
9
10
11
GET /doc/_search
{
"query": {
"bool": {
"must": [
{"term": { "tag": "a" }}
],
...其他查询条件
}
}
}

新建文档时记录tag字段的count:

1
2
3
4
5
PUT /doc/1
{
"tag": ["a", "b"],
"count": 2
}

匹配的时候,同时要匹配count:

1
2
3
4
5
6
7
8
9
10
11
12
GET /doc/_search
{
"query": {
"bool": {
"must": [
{"term": { "tag": "a" }},
{"term": { "count": 1 }}
],
...其他查询条件
}
}
}

boosting

boosting是控制相关度的一种方法:

  • boost > 1时,打分的相关度相对性提升
  • 0 < boost < 1时,打分的权重相对性降低
  • boost < 0时,贡献负分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
GET /doc/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": {
"query": "a",
"boost": 2
}
}
},
{
"match": {
"content": {
"query": "a",
"boost": 1.1
}
}
}
]
}
}
}

其他结构化查询语句

  • range(范围查询):查询找出那些落在指定区间内的数字或者时间,gt 大于、gte 大于等于、lt 小于、lte 小于等于;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    GET /megacorp/employee/_search
    {
    "query" : {
    "bool": {
    "must": {
    "match" : {
    "last_name" : "smith"
    }
    },
    "filter": {
    "range" : {
    "age" : { "gt" : 30 }
    }
    }
    }
    }
    }
  • exists 和 missing:指定字段有值或无值,但后来的版本中废弃了 missing,因为可以通过 must_not+exists 来实现类似的效果

    1
    { "exists" : { "field" : "age" }}
  • about(全文搜索)
    搜索结果将根据相关性排序。

  • match_phrase(短语搜索)
    针对”rock climbing”这样的短语,如果直接使用 match 会分别使用”rock”和”climbing”两个单词进行搜索,而使用 match_phrase 则会直接使用整个短语进行搜索。

  • highlight(高亮)
    指定某个属性中的匹配部分应该被高亮(结果会使用标签包裹)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    GET /megacorp/employee/_search
    {
    "query" : {
    "match_phrase" : {
    "about" : "rock climbing"
    }
    },
    "highlight": {
    "fields" : {
    "about" : {}
    }
    }
    }
  • 前缀查询
    prefix

  • 正则表达式查询
    regexp

  • 通配符匹配
    使用*匹配字符序列(包括空字符),使用?匹配单个字符。
    最好不要把通配符放在前面,这样会很慢。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    GET mts_sample_order/orders/_search
    {
    "from": 1,
    "size": 10,
    "query": {
    "wildcard": {
    "sample_type_name": "咽拭子*"
    }
    }
    }