ElasticSearch Query DSL之term-level queries

概述

注:

  1. 本篇博客的内容参考自 es 7.x版本的官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.x/term-level-queries.html
  2. 博客中的所有代码示例,都已经在7.1.1版本的es集群中验证通过

term-level查询,通常使用在结构化数据的查询中,是用于精确值查找的。

所谓的结构化数据,指的是比如数值(short、integer、long、float、double等8种类型)、日期(date)、keyword、boolean、ip等类型。

跟全文检索不一样的是,term-level查询不会对输入进行分词,相反的,它会直接将输入作为精确值,去做精确匹配查找

term-level查询有如下几种类型:

  1. term query

    term query 查找 指定字段中包含了某个精确值的文档,类似于java中xxSet.contains(“xxx”)。

  2. terms query

    查找指定字段中包含 查询词根集合中 的任意一个精确值的文档。类似于java中的xxSet.contains(“xxx_1”) or xxSet.contains(“xxx_2”)。

  3. terms_set query

    查找与一个或多个指定词根相匹配的文档,而匹配的项的数量则可以由参数来指定。

  4. range query

    范围查询
    类似于如下sql

    select * from tb where price >= 10 and price < 20
    
  5. exists query

    返回在原始字段中至少有一个非空值的文档
    类似于如下sql

    select * from tb where dept_id is null
    或者
    select * from tb where dept_id is not null
    
  6. prefix query

    前缀查询
    类似于如下sql

    select * from tb where name like '苏%'
    
  7. wildcard query

    通配符查询

  8. fuzzy query

    模糊查询

  9. regexp query

    正则表达式查询

  10. type query

    指定类型查询

  11. 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"
      }
    }
  ]
}

  1. 对于索引中的skill_count和skill_required_matches字段,若是不理解这两个字段在业务意义、以及在term-level query中分别起到什么作用,那也没关系,只管先插入进去,等到后面讲到特定部分的时候,用到了这两个字段时自然就清楚了。
  2. 每一行数据,表示的就是一个用户的信息,包括: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为12的doc
GET idx-susu-test-term-query/_search 
{
    
    
  "query": {
    
    
    "term": {
    
    
      "dept": {
    
    
        "value": "BIGDATA"
      }
    }
  }
}


#4. 在boolean 类型字段上使用term query查询,查询isBoss为true的数据,本例中,能从样例数据中查询到id为910的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为910的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 containone or more exact termsin a provided field., 即:terms query 查找指定字段中包含 查询词根集合中 的任意一个 精确值的文档。类似于java中的xxSet.contains(“xxx_1”) or xxSet.contains(“xxx_2”)。

terms 查询好比是 term 查询的复数形式(以英语名词的单复数做比)。它几乎与 term 的使用方式一模一样,如下是两个使用示例及其语义:

# 查询 "salary字段值为2000 或 20000" 的文档,此时会查询到id为78的doc
GET idx-susu-test-term-query/_search
{
    
    
  "query": {
    
    
    "terms": {
    
    
      "salary": [
        2000,
        20000
      ]
    }
  }
}


# 查询 "skill字段值包含DRINK或PS" 的文档,此时会查询到id为7910的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,我们能执行类似如下的查询:

  1. 查询 掌握[“HADOOP”, “JAVA”, “PYTHON”]中任意项 的文档。
  2. 查询 只掌握[“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字段的值。
接下来我们去遍历索引中的每一个文档,模拟一下上述查询的处理过程:

  1. 对于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的文档满足该查询条件。
  2. 对于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,满足查询条件
  3. 对于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.,即通过该参数,我们可以自定义个脚本,来计算出这个最小匹配数
通过该参数,基于样例数据我们可以实现如下查询

  1. 查询匹配[ “HADOOP”, “JAVA”, “PYTHON” ]中的任意2项的文档

    # 查询匹配[ "HADOOP", "JAVA", "PYTHON" ]中的任意2项的文档,此时能查询到id为125的文档
    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

  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的脚本,通过计算参数的个数,来指定这个最小匹配数

  1. 更复杂的脚本示例
    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参数允许我们通过自定义脚本的方式,来计算出最小匹配数,而具体该怎么书写,则需要依据业务意义而定。

  1. 查询包含且仅包含[ “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=25的文档
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版本的官网

  1. 空字符串,例如"“或”-"
  2. 包含null和另一个值的数组,例如[null, “foo”]
  3. 自定义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为910的所有文档
# 可以看到,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=68之外的其他文档
# 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进行查询将查不到文档:

  1. The field in the source JSON is null or []
    也就是当某个字段的值被设置为null或者[]时。这个咱再上面的5.1和5.2查询中已经验证了。

  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之后的某个版本中,具有该特性的吧

  3. The length of the field value exceeded an ignore_above setting in the mapping
    当字段的值已经超出了ignore_above设置的上限时。这个是什么原理呢……应该是跟ignore_above参数有关,可以自己去查询一下吧

    这一点儿倒是真的,已经在7.1.1版本的es集群上做过验证了

  4. 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为12的文档,与结论2不符!
GET idx-susu-test-exists/_search
{
    
    
  "query": {
    
    
    "exists": {
    
    
      "field": "dept"
    }
  }
}

# 6. 基于dept字段做not exists查询,结果能查询到id为34的文档
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为24的文档,与结论3相符
GET idx-susu-test-exists/_search
{
    
    
  "query": {
    
    
    "exists": {
    
    
      "field": "name"
    }
  }
}


# 基于name字段做not exists查询,结果能查询到id为13的文档,与结论3相符
GET idx-susu-test-exists/_search
{
    
    
  "query": {
    
    
    "bool": {
    
    
      "must_not": [
        {
    
    
          "exists": {
    
    
            "field": "name"
          }
        }
      ]
    }
  }
}

6. 模糊查询

这个还没来得验证,后面补上……

6.1 prefix query

6.2 wildcard query

6.3 fuzzy query


7. regexp query

这个还没来得验证,后面补上……


8. type query

指定类型查询。

根据官网,这种查询从7.0.0版本就已经开始被弃用了

通过该查询,我们可以筛选出与提供的文档/映射类型匹配的文档。
查询如下:

GET idx-susu-test-term-query/_search
{
    
    
  "query": {
    
    
    "type": {
    
    
      "value": "_doc"
    }
  }
}

9. ids query

根据index的 _id 字段检索文档,如下所示:

# 查询_id为19的文档
GET idx-susu-test-term-query/_search
{
    
    
  "query": {
    
    
    "ids" : {
    
    
      "values" : ["1", "9"]
    }
  }
}

猜你喜欢

转载自blog.csdn.net/qq_20068025/article/details/120595550