概述
注:
- 本篇博客的内容参考自 es 7.x版本的官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.x/term-level-queries.html
- 博客中的所有代码示例,都已经在7.1.1版本的es集群中验证通过
term-level查询,通常使用在结构化数据的查询中,是用于精确值查找的。
所谓的结构化数据,指的是比如数值(short、integer、long、float、double等8种类型)、日期(date)、keyword、boolean、ip等类型。
跟全文检索不一样的是,term-level查询不会对输入进行分词,相反的,它会直接将输入作为精确值,去做精确匹配查找
。
term-level查询有如下几种类型:
-
term query
term query 查找 指定字段中
包含
了某个精确值的文档,类似于java中xxSet.contains(“xxx”)。 -
terms query
查找指定字段中
包含
查询词根集合中 的任意一个精确值的文档。类似于java中的xxSet.contains(“xxx_1”) or xxSet.contains(“xxx_2”)。 -
terms_set query
查找与一个或多个指定词根相匹配的文档,而匹配的项的数量则可以由参数来指定。
-
range query
范围查询
类似于如下sqlselect * from tb where price >= 10 and price < 20
-
exists query
返回在原始字段中至少有一个非空值的文档
类似于如下sqlselect * from tb where dept_id is null 或者 select * from tb where dept_id is not null
-
prefix query
前缀查询
类似于如下sqlselect * from tb where name like '苏%'
-
wildcard query
通配符查询
-
fuzzy query
模糊查询
-
regexp query
正则表达式查询
-
type query
指定类型查询
-
ids query
ids数组查询。
通俗易懂的说,上面的11
中查询类型,也就是接下来要详细讲解的东西,对比到sql上,其实就是教大家在查询的时候如何去写where条件的,而且是:在面对各种数据类型的字段、或者是特定某类查询的时候,该如何正确的书写where条件。并且,本篇博客所讲的,还是where条件的最小单元
——也就说,举个例子,当我们讲解到term query的时候,我们只会讲到如何去查询dept = “IT”,而不会讲到复合查询如dept = “IT” or dept = “BIGDATA”。
样例数据
在开始之前,为了后面的测试方便,这里我统一设置了一个名为idx-susu-test-term-query的索引,使用手动设置mappings的方式,尽可能多的插入了不同类型的字段,并且也插入了尽可能多情形的数据。
# 1.为了防止index已存在,先删除一下
DELETE idx-susu-test-term-query
# 2.手动设置index的mapping
PUT idx-susu-test-term-query/
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"birth": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"salary": {
"type": "float"
},
"detp": {
"type": "keyword"
},
"skill": {
"type": "keyword"
},
"skill_count": {
"type": "short"
},
"skill_required_matches": {
"type": "integer"
},
"addr": {
"type": "text",
"analyzer": "ik_max_word"
},
"isBoss": {
"type": "boolean"
}
}
}
}
# 3.批量插入数据,同时刷新index
POST idx-susu-test-term-query/_bulk?refresh=true
{ "index": { "_id": "1" }}
{ "id": 1, "birth": "1980-01-01 00:00:00", "salary": 40000, "dept": "BIGDATA", "skill": ["HADOOP", "JAVA", "PYTHON"], "skill_count": 3, "skill_required_matches": 2, "addr": "北京昌平区", "isBoss": false}
{ "index": { "_id": "2" }}
{ "id": 2, "birth": "1985-02-01 10:20:00", "salary": 35000, "dept": "BIGDATA", "skill": ["JAVA", "PYTHON"], "skill_count": 2, "skill_required_matches": 2, "addr": "北京海淀区", "isBoss": false}
{ "index": { "_id": "3" }}
{ "id": 3, "birth": "1989-11-01 05:00:10", "salary": 50000, "dept": "SALE", "skill": ["PPT", "EXCEL"], "skill_count": 2, "skill_required_matches": 1, "addr": "北京朝阳区", "isBoss": false}
{ "index": { "_id": "4" }}
{ "id": 4, "birth": "1990-01-01 11:11:11", "salary": 40000, "dept": "IT", "skill": ["HADOOP", "PHP", "C++"], "skill_count": 3, "skill_required_matches": 3, "addr": "北京昌平区", "isBoss": false}
{ "index": { "_id": "5" }}
{ "id": 5, "birth": "1991-06-01 12:12:12", "salary": 30000, "dept": "IT", "skill": ["HADOOP", "JAVA", "C++"], "skill_count": 3, "skill_required_matches": 4, "addr": "北京昌平区", "isBoss": false}
{ "index": { "_id": "6" }}
{ "id": 6, "birth": "2001-03-01 13:13:13", "salary": 40000, "dept": "IT", "skill": null, "skill_count": 0, "skill_required_matches": 0, "addr": "北京昌平区", "isBoss": false}
{ "index": { "_id": "7" }}
{ "id": 7, "birth": "1995-09-01 16:16:16", "salary": 20000, "dept": "UI", "skill": [null, "EXCEL"], "skill_count": 2, "skill_required_matches": 1, "addr": "上海浦东", "isBoss": false}
{ "index": { "_id": "8" }}
{ "id": 8, "birth": "2000-11-01 20:01:01", "salary": 2000, "dept": "", "skill": [], "skill_count": 0, "skill_required_matches": 1, "addr": "南京江苏", "isBoss": false}
{ "index": { "_id": "9" }}
{ "id": 9, "birth": "1970-12-01 22:22:22", "salary": 1000000, "dept": null, "skill": ["DRINK", "SMOKE"], "skill_count": 2, "skill_required_matches": 1, "addr": "南京江苏", "isBoss": true}
{ "index": { "_id": "10" }}
{ "id": 10, "birth": "1900-12-01", "salary": 200000.0, "skill": ["DRINK"], "skill_count": 2, "skill_required_matches": 1, "addr": "广东佛山", "isBoss": true}
# 4.全量查询(按id升序排序)
GET idx-susu-test-term-query/_search
{
"sort": [
{
"id": {
"order": "asc"
}
}
]
}
注:
- 对于索引中的skill_count和skill_required_matches字段,若是不理解这两个字段在业务意义、以及在term-level query中分别起到什么作用,那也没关系,只管先插入进去,等到后面讲到特定部分的时候,用到了这两个字段时自然就清楚了。
- 每一行数据,表示的就是一个用户的信息,包括:id、生日(birth)、薪酬(salary)、部门(dept)、掌握的技能列表(skill)、掌握的技能数量(skill_count)、地址(addr)、是否是老板(isBoss)。
1. term query
根据term query官网(7.x版本)的描述, term query 的功能是Returns documents that contain
an exact term
in a provided field, 即:term query查找 指定字段中包含
了某个精确值
的文档。类似于java中xxSet.contains(“xxx”)。
1.1 常见的term query查询
我们可以在如数字类型、date、boolean、keyword等类型上使用term query。
比如:
#1. 在float类型字段上使用term query查询,查询salary为200000.0的数据,本例中,能从样例数据中查询到id=10的doc
GET idx-susu-test-term-query/_search
{
"query": {
"term": {
"salary": {
"value": 200000.0
}
}
}
}
#2. 在date 类型字段上使用term query查询,查询birth为"1985-02-01 10:20:00"的数据,本例中,能从样例数据中查询到id=2的doc
GET idx-susu-test-term-query/_search
{
"query": {
"term": {
"birth": {
"value": "1985-02-01 10:20:00"
}
}
}
}
#3. 在keyword 类型字段上使用term query查询,查询dept为BIGDATA的数据,本例中,能从样例数据中查询到id为1和2的doc
GET idx-susu-test-term-query/_search
{
"query": {
"term": {
"dept": {
"value": "BIGDATA"
}
}
}
}
#4. 在boolean 类型字段上使用term query查询,查询isBoss为true的数据,本例中,能从样例数据中查询到id为9和10的doc
GET idx-susu-test-term-query/_search
{
"query": {
"term": {
"isBoss": {
"value": true
}
}
}
}
1.2 注意一:包含,而不是相等
在上面示例的4个查询中,我们展示了面对不同数据类型时候的term query查询,这很容易就会让人错误
的觉得,term query是类似于如下所示sql的等值(equals)判断查询。
select * from tb where dept = "BIGDATA"
其实这是错误的!一定要了解 term是 包含(contains)判断,而非 等值(equals)判断
裆燃了,如果你在使用term query时,就是在 如本样例数据中的id、salary、birth、dept、isBoss等字段一样,存储都是单值的结构化数据上执行查询,那么为了方便理解,也可以认为在这样的字段查询上,term query查询起到的作用跟equals是等同的。
那么,该如何理解term query是包含(contains)判断,而不是等值(equals)判断这句话呢?
为了引出包含
的概念,我们故意在skill字段上,执行如下的查询。而skill字段中存储不是单值,而是用[]定义的字符串数组类型
!
# 该查询可以查到id为9和10的doc
GET idx-susu-test-term-query/_search
{
"query": {
"term": {
"skill": {
"value": "DRINK"
}
}
}
}
查询结果就是如下的两条数据。
并且,尽管id=9的文档包含除 DRINK 以外的其他词,它还是被匹配并作为结果返回,就是因为,term query查询,是包含(contains)操作!
{ "id": 9, "birth": "1970-12-01 22:22:22", "salary": 1000000, "dept": null, "skill": ["DRINK", "SMOKE"], "addr": "南京江苏", "isBoss": true}
{ "id": 10, "birth": "1900-12-01", "salary": 200000, "skill": ["DRINK"], "addr": "广东佛山", "isBoss": true}
为了更深入的理解为什么term query是contains(包含)操作,下面我们讲一下 term 查询是如何工作的
。
Elasticsearch 会在倒排索引中查找包括某 term 的所有文档,然后构造一个 bitset 。在我们的样例数据中,倒排索引表中会有如下两项:
Token | DocIDs |
---|---|
DRINK | 9,10 |
SMOKE | 9 |
如此一来,当 term 查询匹配标记 DRINK 时,它直接在倒排索引中找到记录并获取相关的文档 ID,如倒排索引所示,这里文档 9和文档 10均包含该标记,所以两个文档会同时作为结果返回。
注意:
由于倒排索引表自身的特性,整个字段是否相等会难以计算,如果确定某个特定文档是否 只(only) 包含我们想要查找的词呢?首先我们需要在倒排索引中找到相关的记录并获取文档 ID,然后再扫描 倒排索引中的每行记录 ,查看它们是否包含其他的 terms 。
·
可以想象,这样不仅低效,而且代价高昂。正因如此, term 和 terms 是 必须包含(must contain) 操作,而不是 必须精确相等(must equal exactly)
。
1.3 注意二:array类型下的精确相等
如果一定期望得到我们前面说的那种行为(即整个字段完全相等),比如我们要查询skill字段中包含且仅包含DRINK的文档,那么单单依靠skill字段,已经无法实现了,最好的方式是增加并索引另一个字段, 这个字段用以存储skill字段包含词项的数量。那么在我们的样例数据中,我们不需要增加这么一个字段,因为在我构造样例数据的时候,就已经添加这么一个字段了,字段名为skill_count,用来表示用户所拥有的技能数量。
在样例数据上,执行如下查询:
GET idx-susu-test-term-query/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"skill": {
"value": "DRINK"
}
}
},
{
"term": {
"skill_count": {
"value": 1
}
}
}
]
}
}
}
如上,这个查询对应的语义就是skill字段中包含且仅包含DRINK的文档,而不是任意一个包含 DRINK的文档了。
1.4 注意三:不要在text类型字段上使用term query查询
注意,不要在text类型的字段上使用term query查询,在text类型的字段上,最好使用match查询。
这是因为ES会对text类型的字段进行分词,将字段拆分为一个个的token,这会使得在text类型的字段上,使用精确值查着时,有时会查不到你想要的数据。
比如在样例数据中,id=10这条数据的addr字段,存储的值为广东佛山,但是如果我们执行如下的查询,则查询不到该条数据
GET idx-susu-test-term-query/_search
{
"query": {
"term": {
"addr": {
"value": "广东佛山"
}
}
}
}
这是因为addr字段中,已经不再包含广东佛山这个精确值了,相反的,它被分词器切分为了["广东", "佛山"]
,如下:
# 使用ik_max_word对输入进行分词
GET idx-susu-test-term-query/_analyze
{
"text": ["广东佛山"],
"analyzer": "ik_max_word"
}
# 分词结果如下:
{
"tokens" : [
{
"token" : "广东",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "佛山",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
}
]
}
相反的,我们要用match query去做查询,如下所示:
# 使用match query去查询text类型字段
GET idx-susu-test-term-query/_search
{
"query": {
"match": {
"addr": "广东佛山"
}
}
}
# 查询结果如下:
{
……(无关键要的内容不展开了)……
"hits" : [
{
"_index" : "idx-susu-test-term-query",
"_type" : "_doc",
"_id" : "10",
"_score" : 2.9761126,
"_source" : {
"id" : 10,
"birth" : "1900-12-01",
"salary" : 200000,
"skill" : [
"DRINK"
],
"addr" : "广东佛山",
"isBoss" : true
}
}
]
}
可以看到,使用 match query去查询时,成功查到了数据。
跟 term query查询不同的是, match query查询会在执行查询之前,先将我们我们的查询内容 广东佛山进行分词,得到["广东", "佛山"]
这两个token,然后在查询时,就会返回 只要包含了这两个token中任意之一 的所有文档。
2. terms query
term 查询对于查找单个值非常有用,但通常我们可能想搜索多个值。 如果我们想要查找salary字段值为 2000 或 20000 的文档,或者我们要查询 skill字段值包含DRINK或PS 的文档,此时该如何处理呢?
此时,因为都是在同一个字段上的或查询,因此除了 利用bool + should来使用多个 term 进行组合查询 之外,我们还可以使用一个 terms 查询
(注意末尾的 s )来完成此功能。
根据terms query官网(7.x版本)的描述, terms query 的功能是Returns documents that contain
one or more exact terms
in a provided field., 即:terms query 查找指定字段中包含
查询词根集合中 的任意一个 精确值
的文档。类似于java中的xxSet.contains(“xxx_1”) or xxSet.contains(“xxx_2”)。
terms 查询好比是 term 查询的复数形式(以英语名词的单复数做比)。它几乎与 term 的使用方式一模一样,如下是两个使用示例及其语义:
# 查询 "salary字段值为2000 或 20000" 的文档,此时会查询到id为7和8的doc
GET idx-susu-test-term-query/_search
{
"query": {
"terms": {
"salary": [
2000,
20000
]
}
}
}
# 查询 "skill字段值包含DRINK或PS" 的文档,此时会查询到id为7、9、10的doc
GET idx-susu-test-term-query/_search
{
"query": {
"terms": {
"skill": [
"DRINK",
"PS"
]
}
}
}
3. terms_set query
根据terms_set query 官网(7.x版本)的描述,terms_set query的功能是Returns documents that contain a minimum number of exact terms in a provided field,即:term set query 查找指定字段中包含 查询词根集合中 的 指定个数的精确值的文档。
从解释上不难看出,terms_set query跟terms query是非常相似的,所不同的是,在terms_set query中,你还可以通过名为”最小匹配项“的字段来指定必须匹配的精确值的个数!比如在我们的样例数据中,skill字段是用[]定义的字符串数组类型
,该字段中存放的是用户所掌握的技能,那么通过terms_set query,我们能执行类似如下的查询:
- 查询 掌握[“HADOOP”, “JAVA”, “PYTHON”]中任意
两
项 的文档。 - 查询 只掌握[“JAVA”, “PYTHON”]这两项技能的文档
terms_set query 在对Array类型的字段做检索时非常有用
那么要控制”最小匹配项“,terms_set query中有两个参数,分别为minimum_should_match_field和minimum_should_match_script,下面我们就依次讲解这两个参数,看看通过他们,都能达到什么样的效果。
3.1 minimum_should_match_field
要用白话的方式去讲解minimum_should_match_field有什么作用,其实是有点儿难度的。没办法,我们这里直接上示例。
执行如下的查询:
GET idx-susu-test-term-query/_search
{
"query": {
"terms_set": {
"skill": {
"terms": [ "HADOOP", "JAVA", "PYTHON" ],
"minimum_should_match_field": "skill_required_matches"
}
}
}
}
上面的查询是什么意思呢?其实是:查询索引中 包含 [ "HADOOP", "JAVA", "PYTHON" ]
中的 指定个数的精确值的文档,那么在这里,所谓的指定个数,指的究竟是多少?可以看到查询中有“minimum_should_match_field”: “skill_required_matches”这么个参数,这个参数,指的就是,对于索引中的每一个文档,该文档的指定个数就是它的skill_required_matches字段的值。
接下来我们去遍历索引中的每一个文档,模拟一下上述查询的处理过程:
- 对于id=1的文档:因为该文档的skill_required_matches字段值为
2
,那么结合“minimum_should_match_field”: “skill_required_matches”的查询参数,可知,对于id=1的文档,它的skill字段必须包含查询条件[ "HADOOP", "JAVA", "PYTHON" ]
中的至少2
个!而该文档的skill字段值为[“HADOOP”, “JAVA”, “PYTHON”],很显然,id=1的文档满足该查询条件。 - 对于id=2的文档:因为该文档的skill_required_matches字段值为
2
,那么结合“minimum_should_match_field”: “skill_required_matches”的查询参数,可知,对于id=2的文档,它的skill字段必须包含查询条件[ "HADOOP", "JAVA", "PYTHON" ]
中的至少2
个!而该文档的skill字段值为[“JAVA”, “PYTHON”],很显然,id=2的文档包含了查询条件中的2
个term,满足查询条件 - 对于id=4的文档:因为该文档的skill_required_matches字段值为
3
,那么结合“minimum_should_match_field”: “skill_required_matches”的查询参数,可知,对于id=4的文档,它的skill字段必须包含查询条件[ "HADOOP", "JAVA", "PYTHON" ]
中的至少3
个!而该文档的skill字段值为[“HADOOP”, “PHP”, “C++”],很显然,id=4的文档只包含了查询条件中的1
个term,是不满足“minimum_should_match_field”: “skill_required_matches”条件的!因此该文档不会被查询出来!
查询结果:
{
……
"hits" : [
{
"_index" : "idx-susu-test-term-query",
"_type" : "_doc",
"_id" : "2",
"_score" : 3.0271318,
"_source" : {
"id" : 2,
"birth" : "1985-02-01 10:20:00",
"salary" : 35000,
"dept" : "BIGDATA",
"skill" : [
"JAVA",
"PYTHON"
],
"skill_count" : 2,
"skill_required_matches" : 2,
"addr" : "北京海淀区",
"isBoss" : false
}
},
{
"_index" : "idx-susu-test-term-query",
"_type" : "_doc",
"_id" : "1",
"_score" : 2.7558863,
"_source" : {
"id" : 1,
"birth" : "1980-01-01 00:00:00",
"salary" : 40000,
"dept" : "BIGDATA",
"skill" : [
"HADOOP",
"JAVA",
"PYTHON"
],
"skill_count" : 3,
"skill_required_matches" : 2,
"addr" : "北京昌平区",
"isBoss" : false
}
}
]
}
}
通过上面的例子,我们就讲清楚了terms_set query查询中minimum_should_match_field这个参数作用了。
就我感觉来说,该参数使用与如下场景:对于每个文档,需要匹配的数量不一致时。
如果所有文档需要匹配的数量一致,可以使用下面的minimum_should_match_script参数来替代。
3.2 minimum_should_match_script
对于该参数,官方解释是Custom script containing the number of matching terms required to return a document.,即通过该参数,我们可以自定义个脚本,来计算出这个最小匹配数
。
通过该参数,基于样例数据我们可以实现如下查询
-
查询匹配[ “HADOOP”, “JAVA”, “PYTHON” ]中的任意2项的文档
# 查询匹配[ "HADOOP", "JAVA", "PYTHON" ]中的任意2项的文档,此时能查询到id为1、2、5的文档 GET idx-susu-test-term-query/_search { "query": { "terms_set": { "skill": { "terms": [ "HADOOP", "JAVA", "PYTHON" ], "minimum_should_match_script": { "source": "2" }, "boost": 1.0 } } } }
可以看到,在minimum_should_match_script的source参数中,直接写死了入参为
2
! -
查询包含条件[ “HADOOP”, ……, “PYTHON” ]中的所有项的文档
# 查询包含条件[ "HADOOP", ……, "PYTHON" ]中的所有项的文档 GET idx-susu-test-term-query/_search { "query": { "terms_set": { "skill": { "terms": [ "HADOOP", "JAVA", "PYTHON" ], "minimum_should_match_script": { "source": "params.num_terms" }, "boost": 1.0 } } } }
可以看到,在minimum_should_match_script的source参数为params.num_terms的脚本,而不是写死了值,这是因为我们要查询的是 包含了查询条件中的所有项的文档!而有的时候查询条件是[ “HADOOP”, “JAVA”, “PYTHON” ],有的时候又是[ “HADOOP”, “PYTHON” ],就是说查询条件的元素个数,是动态的。这个时候我们就可以使用params.num_terms的脚本,通过计算参数的个数,来指定这个最小匹配数
- 更复杂的脚本示例
GET idx-susu-test-term-query/_search { "query": { "terms_set": { "skill": { "terms": [ "HADOOP", "JAVA", "PYTHON" ], "minimum_should_match_script": { "source": "Math.min(params.num_terms, doc['skill_required_matches'].value)" }, "boost": 1.0 } } } }
总之呢,就是说minimum_should_match_script参数允许我们通过自定义脚本的方式,来计算出最小匹配数
,而具体该怎么书写,则需要依据业务意义而定。
- 查询包含且仅包含[ “JAVA”, “PYTHON” ]这2项的文档
要完成该功能的查询,就不能仅仅使用term-level query了,需要使用复合查询来完成,如下所示,通过bool + must的组合条件的方式,首先根据第一个条件,查询出所有包含[ “JAVA”, “PYTHON” ]条件中2项的文档此时id=1和2的文档都会被查询出来;同时又根据第二个条件的skill_count=2,就把id=1的文档给过滤掉了。最后就只有id=2的文档被查询出来了,而这正好是我们想要的结果!GET idx-susu-test-term-query/_search { "query": { "bool": { "must": [ { "terms_set": { "skill": { "terms": [ "JAVA", "PYTHON" ], "minimum_should_match_script": { "source": "2" }, "boost": 1.0 } } }, { "term": { "skill_count": { "value": 2 } } } ] } } }
4. range query
根据range query官网(7.x版本),range query的功能是Returns documents that contain terms within a provided range., 即:通过range query,我们能查询某个字段的值在指定范围内的文档,比如查询salary在30000到40000之间的文档数据。
4.1 数值类型上的range query
显而易见的,可以基于数值类型来进行范围查询,示例如下:
# 查询30000<=salary<40000之间的员工数据,可以查询到id=2和5的文档
GET idx-susu-test-term-query/_search
{
"query": {
"range": {
"salary": {
"gte": 30000,
"lt": 40000
}
}
}
}
查询的比较符号,有如下4个:
- gt 大于
- gte 大于等于
- lt 小于
- lte 小于等于
4.2 date类型上的range query
示例如下:
# 查询createAt在最近的1个小时内的数据,因为执行此查询时,时间是2021-10-04 00:46:00,因此能查询到id=1的文档
GET idx-susu-test-date-range-query/_search
{
"query": {
"range": {
"createAt": {
"gte": "now-1h"
}
}
}
}
关于在date类型上做range query,我自己在实践中没用过,这就不写了,关于这个,可以去查看这个博客:https://blog.csdn.net/weixin_42652596/article/details/110230318
5. exists query
根据exists query官网(7.x版本)的描述,exists query的功能为Returns documents that contain an indexed value for a field——如果是根据这个英文来直译的话,就是:当我们在索引的某个字段上执行exists判断时,对于索引中的某条数据来说,若该条数据中的这个字段的值是一个能够被索引到的值,那么该文档就会被查询到!
可以看到,上面直译的结果非常的拗口,我们换一种解释:exists query就是查找指定字段包含任何非空值【不是null 也不是[ ]】的文档,类似于mysql 的 is not null判断。
注意:这些值不属于空值(根据7.1
版本的官网)
- 空字符串,例如"“或”-"
- 包含null和另一个值的数组,例如[null, “foo”]
- 自定义null-value,在字段映射中定义
原理
正如前面所说的,exists query就是查找指定字段包含任何非空值【不是null 也不是[ ]】的文档。
反过来也就是说,如果一条数据的指定字段的值是空值(即:null或者[],又或者这条数据就没有该字段),那么通过exists查询,是查不到这条数据的! —— 这是为什么呢?
问:如果一个字段没有值,那么如何将它存入倒排索引中的呢?
这是个有欺骗性
的问题,因为答案是:什么都不存
!
让我们看看在我们的样例数据中,id=9和10的文档的skill字段,在倒排索引表中的内容:
Token | DocIDs |
---|---|
DRINK | 9,10 |
SMOKE | 9 |
那么如何将某个不存在的字段存储在这个数据结构中呢?无法做到!简单的说,
一个倒排索引只是一个 token 列表和与之相关的文档信息,如果字段不存在,那么它也不会持有任何 token,也就无法在倒排索引结构中表现
。
最终,这也就意味着,null, [] (空数组)和 [null] 所有这些都是等价的,它们无法存于倒排索引中。
这也就是为什么,当进行exists query查询时,若某条文档的指定字段是空值的时候该文档将不会被搜到的原因。因为你进行exists query查询时,ES从倒排索引表中根本找不到该文档!
5.1 exists示例1
# 在【dept】字段上,做exists查询
# 可以查询到,可以查询到除了id为9和10的所有文档
# 可以看到,id=8的文档也被查询出来了,这是因为""是空字符串,而“空字符串”不是空值,因此能查询到id=8的doc
# 而id=9的文档,dept字段的值为null;id=10的文档,直接没有dept字段。那么这两个文档,都会被过滤掉
GET idx-susu-test-term-query/_search
{
"query": {
"exists": {
"field": "dept"
}
}
}
5.2 exists示例2
# 在【skill】字段上,做exists查询
# 可以查询到,除了id=6和8之外的其他文档
# id=6的文档,skill字段的值为null;而id=8的文档,skill字段的值为[],是个空数组。因此这两个文档都会被过滤掉
GET idx-susu-test-term-query/_search
{
"query": {
"exists": {
"field": "skill"
}
}
}
5.3 not exists示例
要进行not exists判断,也就是要进行类似于mysql中的is null判断,则需要通过使用boolean query + must_not 结合exists来实现了,示例如下:
# 查询【skill】字段为空的文档
GET idx-susu-test-term-query/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "skill"
}
}
]
}
}
}
5.4 注意事项
根据7.x版本的官网描述,当出现如下几种情况时,用exists query进行查询将查不到文档:
-
The field in the source JSON is null or []
也就是当某个字段的值被设置为null或者[]时。这个咱再上面的5.1和5.2查询中已经验证了。 -
The field has “index” : false set in the mapping
即,当某个字段的mapping中的index参数被设置为false时,那么对该字段做exists query,则永远查询不到文档——注意是所有文档都查询不到,而不管你是用exists还是not exits查询!这是因为当中唉mapping中将index参数设置为false,es就不允许在该字段上进行查询,因此,无论你在字段上进行的是exists还是not exists查询,都查不到任何数据!注:我在7.1.1版本的es集群上对这一点进行了验证,发现并不是这样的……但上述事项,确实是es 7.x版本的官网的描述,那么唯一的描述应该是,在7.1.1之后的某个版本中,具有该特性的吧
-
The length of the field value exceeded an ignore_above setting in the mapping
当字段的值已经超出了ignore_above设置的上限时。这个是什么原理呢……应该是跟ignore_above参数有关,可以自己去查询一下吧这一点儿倒是真的,已经在7.1.1版本的es集群上做过验证了
-
The field value was malformed and ignore_malformed was defined in the mapping
这个没试过
下面我们重新造一批数据,来验证上面的2和3点:
# 1.为了防止index已存在,先删除一下
DELETE idx-susu-test-exists
# 2.手动设置index的mapping
PUT idx-susu-test-exists/
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"dept": {
"type": "keyword",
"index": false
},
"name": {
"type": "keyword",
"ignore_above": 5
}
}
}
}
# 3.批量插入数据,同时刷新index
POST idx-susu-test-exists/_bulk?refresh=true
{ "index": { "_id": "1" }}
{ "id": 1, "dept": "IT", "name": "zhangsan"}
{ "index": { "_id": "2" }}
{ "id": 2, "dept": "", "name": "lisi"}
{ "index": { "_id": "3" }}
{ "id": 3, "dept": null, "name": "wangwu"}
{ "index": { "_id": "4" }}
{ "id": 4, "name": "susua"}
# 4. 查询所有数据
GET idx-susu-test-exists/_search
{}
# 5. 基于dept字段做exists查询,结果能查询到id为1和2的文档,与结论2不符!
GET idx-susu-test-exists/_search
{
"query": {
"exists": {
"field": "dept"
}
}
}
# 6. 基于dept字段做not exists查询,结果能查询到id为3和4的文档
GET idx-susu-test-exists/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "dept"
}
}
]
}
}
}
# 7. dept字段确实已经被设置为index=false了,因为在dept上执行查询操作时,确实已经将index设置为false了
# 因为执行如下查询是已经报错了:Cannot search on field [dept] since it is not indexed
GET idx-susu-test-exists/_search
{
"query": {
"term": {
"dept": {
"value": "IT"
}
}
}
}
# 8. 基于name字段做exists查询,结果能查询到id为2和4的文档,与结论3相符
GET idx-susu-test-exists/_search
{
"query": {
"exists": {
"field": "name"
}
}
}
# 基于name字段做not exists查询,结果能查询到id为1和3的文档,与结论3相符
GET idx-susu-test-exists/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "name"
}
}
]
}
}
}
6. 模糊查询
6.1 prefix query
根据官网prefix query官网(7.x版本)描述,prefix queryReturns documents that contain a specific prefix in a provided field.,即prefix query查询的是指定字段上包含有特定前缀的doc,查询效果类似于如下sql:select * from tb where name like ‘苏%’。
使用示例
# 在keyword类型字段上执行查询:查询dept字段上以"B"为前缀的doc,可以查询到id为1和2的doc
GET idx-susu-test-term-query/_search
{
"query": {
"prefix": {
"dept": {
"value": "B"
}
}
}
}
# 在text类型字段上执行查询:查询addr字段中以"南京"作为前缀的doc,可以查询到id为8和9的doc
GET idx-susu-test-term-query/_search
{
"query": {
"prefix": {
"addr": {
"value": "南京"
}
}
}
}
注:仅仅可以使用在
text
和keyword
类型的字段上执行prefix query,假若在其他类型字段上执行prefix query则会报错如下:
# 在date类型上执行prefix查询,将会报错
GET idx-susu-test-term-query/_search
{
"query": {
"prefix": {
"birth": {
"value": "1990"
}
}
}
}
报错如下:
{
"error": {
"root_cause": [
{
"type": "query_shard_exception",
"reason": "Can only use prefix queries on keyword and text fields - not on [birth] which is of type [date]",
"index_uuid": "uhSZrphuR4Sy0u_4KUyYnQ",
"index": "idx-susu-test-term-query"
}
],
……
}
其他参数
加快prefix query查询速度
在索引数据量比较大的时候,为了加快prefix query的查询速度,我们在设置索引mappings的时候,可以在字段上指定一个名为index_prefixes的参数。当指定了该参数,则es在插入索引数据的时候,会另外使用一个字段来存储前缀信息(Elasticsearch indexes prefixes between 2 and 5 characters in a separate field),通过这种以空间换时间的方式,来加快prefix query的查询速度。
注意事项
6.2 wildcard query
根据官网wildcard query,wildcard query Returns documents that contain terms matching a wildcard pattern.即wildcard query查询的是指定字段匹配上了给定通配符的doc。
wildcard query中支持如下两种通配操作符:
?
代表任意一个字符*
代表任意的一个或多个字符
使用示例
# 在text类型字段上做查询,如下dsl可以查询到id为8和9的doc
GET idx-susu-test-term-query/_search
{
"query": {
"wildcard": {
"addr": {
"value": "南*",
"boost": 1.0,
"rewrite": "constant_score"
}
}
}
}
另外需要注意的是
- 一定要尽量避免使用如 *北京或?北京这种
以通配符作为开头
的这种查询方式,因为这样会增加es去匹配文档时所需的迭代次数,从而拖慢查询性能。
(Avoid beginning patterns with * or ?. This can increase the iterations needed to find matching terms and slow search performance.
" Note that this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?.")- 仅仅可以在
text
和keyword
类型的字段上执行wildcard query,假若在其他类型字段上执行wildcard query则会报错如下"Can only use wildcard queries on keyword and text fields"
可配参数
boost
可选参数。默认值1.0。
boost参数是一个浮点类型的值,通过该参数,用户可以调整匹配上了指定通配符模式的文档的算分结果。
- 当 boost在0和1.0之间时,匹配上了指定通配符模式的doc的算分结果将会有所
降低
- 当 boost大于1.0时,匹配上了指定通配符模式的doc的算分结果将会有所
提高
rewrite
可选参数。
rewrite参数用于控制es在执行wildcard时的一些具体计算方式,比如是否计算相关性评分等。
该参数是为进阶型用户(This parameter is for expert users only
)准备的,若想知道该参数的具体用法,请参考官网。在日常使用中若无特殊需求,使用默认值constant_score即可
注意事项
在使用wildcard查询之前,一定要先看下面的两篇博客,确保使用wildcard前,已经明白了使用wildcard query可能会导致的问题!
Elasticsearch 警惕使用 wildcard 检索!然后呢?
es ElasticSearch集群故障案例分析: 警惕通配符查询 Wildcard
6.3 fuzzy query
这个还没来得验证,后面补上……
7. regexp query
根据官网, regexp query Returns documents that contain terms matching a regular expression.,即regexp query查询返回的是匹配上了指定正则表达式的doc。
这个还没来得验证,后面补上……
8. type query
指定类型查询。
根据官网,这种查询从7.0.0版本就已经开始被弃用了
通过该查询,我们可以筛选出与提供的文档/映射类型匹配的文档。
查询如下:
GET idx-susu-test-term-query/_search
{
"query": {
"type": {
"value": "_doc"
}
}
}
9. ids query
根据index的 _id 字段检索文档,如下所示:
# 查询_id为1和9的文档
GET idx-susu-test-term-query/_search
{
"query": {
"ids" : {
"values" : ["1", "9"]
}
}
}