通过上一篇文章我们知道,在全文搜索的时候,系统会对检索内容进行分词,然后再对每个词项进行检索。但是我们今天介绍的基于词项查询的 API 是不需要对输入内容进行分词的,Term Level Query 会将输入的内容会作为一个整体来进行检索,并且使用相关性算分公式对包含整个检索内容的文档进行相关性算分

Term 是文本经过分词处理后得出来的词项,是 ES 中表达语义的最小单位。ES 中提供很多基于 Term 的查询功能,下面几个 API 是我们今天将会介绍的:

  • Term Query,返回在指定字段中准确包含了检索内容的文档。
  • Terms Query,跟 Term Query 类似,不过可以同时检索多个词项的功能。
  • Range Query,范围查询。
  • Exist Query,返回在指定字段上有值的文档,一般用于过滤没有值的文档。
  • Prefix Query,返回在指定字段中包含指定前缀的文档。
  • Wildcard Query,通配符查询。

为了更好地理解各个例子,我们还是以在线书店为例子,并且沿用上一篇文档中的数据。下面就正式开始今天的内容吧。

一、Term Level Query API 介绍

1. Term Query

Term Query API 返回在指定字段中准确包含了检索内容的文档,你可以使用此 API 去查询精确值的字段,如书本 ID、价格等。其示例如下:

  1. # 使用 Term Query API 查询书本 id 为 "4ee82463" 的文档
  2. POST books/_search
  3. {
  4. "query": {
  5. "term": {
  6. "book_id": {
  7. "value": "4ee82463"
  8. }
  9. }
  10. }
  11. }

如上示例,查询 “book_id” 为 “4ee82463” 的文档,将会返回文档 ID 为 2 的文档信息。

需要注意的是,要避免将 Term Query 用在 text 类型的字段上,例如下面示例,搜索书名中含有 “Linux” 的文档:

  1. # 使用 Term Query API,搜索书名中含有 Linux 的文档
  2. POST books/_search
  3. {
  4. "query": {
  5. "term": {
  6. "name": {
  7. "value": "Linux"
  8. }
  9. }
  10. }
  11. }

如上示例,我们的数据中确实有两个文档的书名里含有 “Linux”,但是却无法匹配上。

上面说过,基于 Term 的查询是不会对检索内容进行分词的,输入的文本会作为一个整体进行查询。但是索引数据的时候是会进行分词并且转化为小写的(书名字段使用了 standard 分词器,会进行小写转换),所以上述的例子是无法匹配上任何文档的。需要将检索内容转为小写的 “linux” 才可匹配上文档。如果要对 text 类型的字段进行搜索,应该使用 match API 而不是 Term Query API。

2. Terms Query

Terms Query 的功能跟 Term Query 类似,不过可以同时检索多个词项的功能。例如我需要检索作者为 “Stephen Hawking” 或者 “Wolfgang Mauerer” 写的书的时候,可以这样做:

  1. # 使用 Terms Query 进行查询
  2. POST books/_search
  3. {
  4. "query": {
  5. "terms": {
  6. "author": [ # 数组,可以指定多个作者的名字
  7. "Stephen Hawking",
  8. "Wolfgang Mauerer"
  9. ]
  10. }
  11. }
  12. }

如上示例,”author” 字段的格式是个数组,可以指定多个作者的名字。

3. Range Query

Range Query API 可以查询字段值符合某个范围的文档数据。例如查询书本价格大于等于 10.0 小于 20.0 的书本信息。其示例如下:

  1. # 使用 Range Query 查询书本价格大于等于 10.0 小于 20.0 的书本
  2. POST books/_search
  3. {
  4. "query": {
  5. "range": {
  6. "price": {
  7. "gte": 10.0,
  8. "lt": 20.0
  9. }
  10. }
  11. }
  12. }

如上示例,Range Query API 是比较简单的,在 “range” 字段中指定需要查询的文档字段,这里我们查询 “price” 字段,并且规定其值大于等于 10.0,小于 20.0。

对于大小的比较可以查看以下列表:

  • gt:表示大于
  • gte: 表示大于或者等于
  • lt: 表示小于
  • lte: 表示小于或者等于

4. Exist Query

使用 Exist Query API 可以查询那些在指定字段上有值的文档,一般情况下会使用这个 API 来做文档过滤。

那什么样的值才被认为是空值呢?一个字段的值为空可能是由于下面这个几种原因导致的:

  • 字段的 JSON 值为 null 或者 [],如果一个字段压根不存在于文档的 _source 里,也被认为是空的。
  • 一个字段在 Mapping 定义的时候设置了 “index” : false。
  • 一个字段的值的长度超出了 Mapping 里这个字段设置的 ignore_above 时。
  • 当字段的值不合规,并且 Mapping 中这个字段设置了 ignore_malformed 时。

Exist Query 的使用示例如下:

  1. # 查询出所有存在 "price" 字段的文档
  2. POST books/_search
  3. {
  4. "query": {
  5. "exists": {
  6. "field": "price"
  7. }
  8. }
  9. }
  10. # 查询出所有存在 "press"(出版社) 字段的文档
  11. POST books/_search
  12. {
  13. "query": {
  14. "exists": {
  15. "field": "press"
  16. }
  17. }
  18. }

如上示例,其中第一个查询是可以匹配上相关的文档的,而第二个查询是无法匹配是任何文档,因为 Mapping 中没有定义 “press” 字段,并且没有任何文档中含有 “press” 字段。

5. Prefix Query

Prefix Query 可以查询在指定字段中包含特定前缀的文档。其示例如下:

  1. # 使用 Prefix Query 查询含有 "linu" 前缀的文档
  2. POST books/_search
  3. {
  4. "query": {
  5. "prefix": {
  6. "name": {
  7. "value": "linu"
  8. }
  9. }
  10. }
  11. }

如上示例,使用了 Prefix Query 查询含有 “linu” 前缀的文档,如果书本名字中含有 “linu” 开头的词语的文档将会被匹配上。需要注意的是,text 类型的字段会被分词,成为一个个的 term,所以这里的前缀匹配是匹配这些分词后term!

6. Wildcard Query

Wildcard Query 允许使用通配符表达式进行匹配。Wildcard Query 支持两个通配符:

  • ?,使用 ? 来匹配任意字符。
  • *,使用 * 来匹配 0 或多个字符。

Wildcard Query 使用示例如下:

  1. # 使用 Wildcard Query 查询书名中含有 "linu" 开头的文档
  2. POST books/_search
  3. {
  4. "query": {
  5. "wildcard": {
  6. "name": "linu*"
  7. }
  8. }
  9. }

如上示例,使用了 Wildcard Query 查询书名中含有 “linu” 开头的文档。

需要注意的是,Prefix Query 和 Wildcard Query 在进行查询的时候需要扫描倒排索引中的词项列表才能找到全部匹配的词项,然后再获取匹配词项对应的文档 ID。所以使用 Wildcard Query API 的时候需要注意性能问题,要尽量避免使用左通配匹配模式,如 “*linux”、”.*linux”。

至此,今天要介绍的 6 种 Term Level Query 的 API 已经介绍完毕。平时我们会经常使用这些 API 来对结构化数据进行查询、过滤,这个时候一般不需要对数据进行相关性评分,所以可以跳过评分阶段来提高搜索的性能。那什么是结构化数据呢?怎么样可以跳过相关性评分的阶段呢?请继续往下看!

二、使用 Term Level Query API 进行结构化搜索

结构化搜索指的是对结构化的数据进行搜索。那什么是结构化数据呢?你可以认为是高度组织、格式化的数据,例如日期、颜色、地区编码、价格等等。

像书本的出版日期、价格这些都是有精确的格式的,我们可以对这些数据进行逻辑操作,例如判断价格的范围等。一般我们会对结构化数据进行精确匹配,而精确匹配的结果为布尔值,这个时候可以考虑跳过相关性算分的步骤,从而提高搜索的性能。

使用 Constant Score 可以将 query 转化为 filter,可以忽略相关性算分的环节,并且 filter 可以有效利用缓存,从而提高查询的性能。其示例如下:

  1. # 使用 Range 查询,并且不进行相关性算分
  2. POST /books/_search
  3. {
  4. "query": {
  5. "constant_score": {
  6. "filter": {
  7. "range": {
  8. "price": {
  9. "gte": 10,
  10. "lte": 20
  11. }
  12. }
  13. }
  14. }
  15. }
  16. }

三、总结

今天为你介绍了 Term Level Query 相关的几个API。

基于词项的搜索与基于全文的搜索的一个区别是,基于词项的搜索不会对输入的检索内容进行分词。

可以使用 Term Query 和 Terms Query 检索出指定字段中存在特定词项的文档,使用 Range Query 进行某个字段值的范围查询。Exist Query 可以判断指定字段是否有值,可以用作文档过滤。

可以使用 Prefix Query 和 Wildcard Query 做匹配查询,Prefix Query 做前缀匹配查询,Wildcard Query 做通配匹配查询。需要注意的是使用 Wildcard Query 的时候,避免使用左通配匹配模式,如 “*linux”、”.*linux”,否则可能会严重影响性能。

在我们不需要对查询进行相关性算分的时候,可以使用 Constant Score 可以将 query 转化为 filter,可以忽略相关性算分的环节。

更多关于 Term Level Query API 的使用方式,可以参考官方文档

那今天的内容就到这啦,最后,如果大家对这些 API 的使用还有点生疏的话,可以参考官方文档多实操几次。