Elasticsearch教程(8) Query DSL 查询教程

Query DSL简介

在公司用ES多是因为ES的开箱即用的分布式和查询速度快,用的较多的查询基本就是term level query,全文查询用的不多,本文总结下常用的term level query。ES提供了一个基于JOSN格式的查询语句-Query DSL,它包含2种类型:叶子查询和组合查询。

叶子查询

叶子查询语句是在一个特定字段(field)上查询特定的值(value),例如match,term或range查询,它们可以单独使用。例如:

GET /pigg/_search
{
  "query": {
    "term": {
      "age": 32
    }
  }
}

组合查询

组合查询套在别的叶子查询或组合查询外面,它是用来组合逻辑上复杂多样的查询。例如:

GET /pigg/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "age": 32
          }
        },
        {
          "match": {
            "name": "王"
          }
        }
      ]
    }
  }
}

Query Context 和 Filter Context

一条查询语句的行为取决于是被用在Query Context还是Filter Context

Query Context

  • 在Query Context时,查询语句在问"这个文档匹配程度如何?"。除了判断文档是否匹配,还计算得出一个相关分数放在_score这个元字段上。
  • ES根据相关分数来对结果排序,相关分数表示每个文档的匹配程度,相关分数越高说明文档越匹配。

Filter Context

  • 在Filter Context时,查询语句在问"这个文档是否匹配?"。返回就是简单的是或否,无需计算相关分数,这通常用作过滤结构化数据。
  • 为了提高查询性能,通常Elasticsearch会自动缓存常用的过滤结果。缓存的细节可以看《深入理解ElasticSearch 》,这本书适合看过ES官网后再想了解ES原理时看。
GET /pigg/_doc/_search
{
    "query":{
        "bool":{
            "must":{
                "match":{ //这里的match就是query context,会计算相关分数
                    "first_name":"wang dong"
                }
            },
            "filter":{ //这里的filter就是filtercontext,不计算相关分数
                "range":{
                    "age":{
                        "gt":20,
                        "lt":30
                    }
                }
            }
        }
    }
}

词项查询(Term Level Query)

Term Level Query是在字段上查询精确的值,而不是进行全文匹配(比如match)。通常用到的词项查询有term,terms,ids,range,prefix,exists等等,下面一一介绍。

准备数据

插入5条人员数据,其中interest是兴趣,interest_count是指兴趣的个数。

PUT /pigg/_doc/1
{
  "name": "王冬冬",
  "ename": "winter",
  "age": 32,
  "about": "I am a good coder",
  "interest": ["eat", "coding"],
  "interest_count": 2
}

PUT /pigg/_doc/2
{
  "name": "朱大旬",
  "ename": "vissy",
  "age": 29,
  "about": "I am a tester",
  "interest": ["eat", "testing"],
  "interest_count": 2,
  "job": null
}

PUT /pigg/_doc/3
{
  "name": "王佳冬",
  "ename": "micoo",
  "age": 3,
  "about": "I am a baby",
  "interest": ["eat", "play", "sleep"],
  "interest_count": 3,
  "job": []
}

PUT /pigg/_doc/4
{
  "name": "蓝波",
  "ename": "lanbo",
  "age": 2,
  "about": "I am a cat",
  "interest": ["eat", "sleep"],
  "interest_count": 2,
  "job": ""
}

PUT /pigg/_doc/5
{
  "name": "波波",
  "ename": "bobo",
  "age": 1,
  "about": "I am a cat",
  "interest": ["eat"],
  "interest_count": 1,
  "job": [null]
}

查看mapping

GET /pigg/_mapping

因为没有提前定义mapping,所以是es自生成的mapping。其中name属性如下,name本身是text类型,内部包含一个keyword类型,用精确查询时,就用name.keyword属性。

          "name" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }

1 term

term query判断文档的某个字段(field)是否包含某一个确定的值。一般都是用在结构化数据上,比如keyword,int,long,ip,date等,避免用在text类型的字段上,在text上应该用match匹配查询。

1.1 查询age=32

GET /pigg/_search
{
  "query": {
    "term": {
      "age": 32
    }
  }
}

1.2 查询name.keyword=“王冬冬”

GET /pigg/_search
{
  "query": {
    "term": {
      "name.keyword": "王冬冬"
    }
  }
}

返回结果如下:

注意返回结果里的相关分数_score
  "hits" : {
    "total" : 1,
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "pigg",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,//注意这个相关分数_score
        "_source" : {
          "name" : "王冬冬",
          "ename" : "winter",
          "age" : 32,
          "about" : "I am a good coder",
          "interest" : [
            "eat",
            "coding"
          ],
          "interest_count" : 2
        }
      }
    ]
  }

1.3 过滤name.keyword=“王冬冬”

一般做精确查询的时候,没有必要计算相关分数,在上面讲Filter Context的时候,说过用filter不会计算相关分数,也提高了性能,下面就把上面语句改成filter过滤查询试试。

GET /pigg/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "name.keyword": "王冬冬"
        }
      }
    }
  }
}

结果如下:

  "hits" : {
    "total" : 1,
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "pigg",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.0,//没有计算相关分数,默认0.0
        "_source" : {
          "name" : "王冬冬",
          "ename" : "winter",
          "age" : 32,
          "about" : "I am a good coder",
          "interest" : [
            "eat",
            "coding"
          ],
          "interest_count" : 2
        }
      }
    ]
  }

1.4 为何要避免在text类型上用Term Level Query

name是text类型,默认下es会分词
1.“王冬冬”被分词为“王”,“冬”这2个词项
2.“王佳冬”被分词为“王”,“佳”,“冬”这3个词项
倒排索引如下:

词项文档ID
1,3
1,3
3

如果term查询name=“王冬冬”,因为没有"王冬冬"这个词项,所以下面语句是查不到数据的。

GET /pigg/_search
{
  "query": {
    "term": {
      "name": "王冬冬"
    }
  }
}

1.5 一定要理解term是包含的意思,不是等于

在工作中经常用term查询id=1,status=1等类似的查询,久而久之有term就是等于的错觉,其实不然。
ES里数据如下:

idnameinterestinterest_count
1王冬冬[“eat”, “coding”]2
2朱大旬[“eat”, “testing”]2
3王佳冬[“eat”, “play”, “sleep”]3
4蓝波[“eat”, “sleep”]2
5波波[“eat”]1
查询兴趣为吃(interest=“eat”)的数据
GET /pigg/_search
{
  "query": {
    "term": {
      "interest.keyword": "eat"
    }
  }
}

我们本来以为查询结果就1条id=5的数据,只有波波的兴趣只为“eat”。
然而查询结果返回了5条所有数据,因为每条记录都包含了“eat”。
结果如下:
在这里插入图片描述

1.6 结合interest_count查询interest

interest_count记录这interest数组里元素个数。
结合interest_count查询查询兴趣只有吃(interest=“eat”)的数据。

GET /pigg/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "term":{
                        "interest.keyword":"eat"
                    }
                },
                {
                    "term":{
                        "interest_count":"1"
                    }
                }
            ]
        }
    }
}
注意点:这个只有interest_count=1时才有效
  • 在下面terms查询时,查询(interest=[“eat”, “sleep”] and interest_count=2)时,用上面的组合查询方式是不会达到预期效果。因为term和terms是包含的意思,[“eat”, “coding”]和[“eat”, “testing”]也符合条件,毕竟它们都包含“eat”且interest_count=2。
  • 要想达到预期效果,除了用must+term组成组合查询,还可以用terms_set查询,ES6才有的。

2 terms

  • terms和term类似,它判断文档的某个字段(field)是否包含某一个或多个确定的值(value)。
  • value参数是一个数组,里面包含多个你想要查询的值,只要有一个值命中就算符合
  • value数组里的最多能放65536个值,如果想修改这个限制,可以修改setting里index.max_terms_count这个配置。
term和terms是包含的意思,上面准备数据中interest字段是数组,下面用查询interest这个字段来说明。

2.1 查询interest有play或sleep的人

GET /pigg/_search
{
  "query": {
    "terms": {
      "interest": ["play", "sleep"]
    }
  }
}

查询结果返回id为3和4的数据,因为他们兴趣包含playsleep。

idnameinterestinterest_count
3王佳冬[“eat”, “play”, “sleep”]3
4蓝波[“eat”, “sleep”]2

2.2 用must组合查询interest=[“eat”, “sleep”]

上面也说了用(interest=[“eat”, “sleep”] and interest_count=2)查询是不对的,ES6之前可以用must+term组合查询。相当于interest.contains(“eat”) and interest.contains(“sleep”) and interest_count=2

GET /pigg/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "term":{
                        "interest.keyword":"eat"
                    }
                },
                {
                    "term":{
                        "interest.keyword":"sleep"
                    }
                },
                {
                    "term":{
                        "interest_count":2
                    }
                }
            ]
        }
    }
}

3 terms_set

terms_set查询和terms类似,但它定义了一个最小命中数。比如value数组里有3个值,最小命中数定义为2,说明字段里的值至少命中数组里的2个,才算这个文档符合。而terms查询只要命中数组里任意1个,就算文档符合查询条件。

3.1 至少命中[“eat”, “play”, “sleep”]中2个

GET /pigg/_search
{
  "query": {
     "terms_set": {
       "interest.keyword":{
         "terms": ["eat", "play", "sleep"],
         "minimum_should_match_script": {
             "source": "2"
         }
       }
     }
  }
}

3.2 用terms_set解决interest=[“eat”, “sleep”]

GET /pigg/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "terms_set":{
                        "interest.keyword":{
                            "terms":[
                                "eat",
                                "sleep"
                            ],
                            "minimum_should_match_script":{
                                "source":"2"
                            }
                        }
                    }
                },
                {
                  "term": {
                    "interest_count": {
                      "value": 2
                    }
                  }
                }
            ]
        }
    }
}
  "hits" : {
    "total" : 1,
    "max_score" : 1.8754687,
    "hits" : [
      {
        "_index" : "pigg",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.8754687,//注意这个评分
        "_source" : {
          "name" : "蓝波",
          "ename" : "lanbo",
          "age" : 2,
          "about" : "I am a cat",
          "interest" : [
            "eat",
            "sleep"
          ],
          "interest_count" : 2,
          "job" : ""
        }
      }
    ]
  }

3.3 用terms_set解决interest=[“eat”, “sleep”]且不评分

文章最上面讲过用filter context,这样不评分也提高查询性能,以后如果是无需评分的,最好放到filter里面。

GET /pigg/_search
{
    "query":{
        "bool":{
            "filter":{
                "bool":{
                    "must":[
                        {
                            "terms_set":{
                                "interest.keyword":{
                                    "terms":[
                                        "eat",
                                        "sleep"
                                    ],
                                    "minimum_should_match_script":{
                                        "source":"2"
                                    }
                                }
                            }
                        },
                        {
                            "term":{
                                "interest_count":{
                                    "value":2
                                }
                            }
                        }
                    ]
                }
            }
        }
    }
}

4 ids

根据文档ID查询文档,下面返回1和4两个文档,因为id=100的不存在。

GET /pigg/_search
{
  "query": {
    "ids": {
      "values" : ["1", "4", "100"]
    }
  }
}

5 exists

在MySQL中,常用 is null和 is not null,ES用exists。

下面情况认定字段不存在
  • 源JSON中的字段是null或[]
  • 字段在mapping中设置为"index" : false
  • 字段值的长度超出了mapping中设置的ignore_above
  • 字段值格式错误,并且mapping中设置的了ignore_malformed
下面特殊情况认定字段存在
  • 空字符串,例如"“或”-"
  • 包含null和另一个值的数组,例如[null, “foo”]
  • 在mapping中设置的自定义null-value(如果不熟,避免使用,用不好坑部门同事)

测试数据如下:

idnamejob
1王冬冬缺少job字段
2朱大旬null
3王佳冬[]
4蓝波”“
5波波[null]

5.1 查询job值存在的文档

GET /pigg/_search
{
  "query": {
    "exists": {
        "field": "job"
    }
  }
}

返回结果只有id=4的数据。

5.2 查询job值不存在的文档

利用must_not+exists组合查询,代替之前老版本ES的missing查询

GET /pigg/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "exists": {
              "field": "job"
          }
        }
      ]
    }
  }
}

6 range

range查询判断某个字段是否在某个范围内。range有如下参数:

参数说明
gte>=
gt>
lte<=
lt<
boost查询权重,默认1.0

6.1 查询age在[3,30)之间的数据

GET /pigg/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "age": {
            "gte": 3,
            "lt": 30
          }
        }
      }
    }
  }
}

7 prefix

prefix查询返回在指定字段中包含特定前缀的文档。这个就像我们SQL语句的 like “xx%”。
您可以使用mapping设置中的index_prefixes参数来加快前缀查询的速度。如果启用该参数,Elasticsearch只会为单独的字段索引2到5个字符的前缀。这使Elasticsearch在较大的索引上更有效率执行前缀查询,从而减少成本。
查询ename以“wi”开头的数据。

GET /pigg/_search
{
  "query": {
    "prefix": {
      "ename.keyword": "wi"
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑟 王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值