Elasticsearch 数据查询实践

国破山河在,城春草木深。

1 前言

Elasticsearch 是一个开源的分布式搜索和分析引擎,其强大的查询能力支撑了复杂的业务查询。其领域特定语言DSL(Domain Specific Language)是通过 JSON 构建的,非常灵活的支持多种方式的查询。本文将结合业务实践,总结使用 ES 查询 DSL 的结构和常用的查询方法,巩固使用 ES 查询数据的基础知识。

2 DSL 介绍

DSL 常用的搜索操作包括:

  • 全文搜索 对文本字段执行搜索,支持短语搜索和模糊匹配。
  • 结构化搜索 对结构化的数据字段进行搜索,比如日期、数字、枚举等。
  • 复合查询 多种查询条件的组合,比如 bool 查询,通常用来执行复杂的搜索条件。
  • 数据聚合 对数据进行聚合查询,用于执行统计信息。
  • 结果排序 对查询的结果根据一个或者多个字段进行排序后展示。
  • 数据分页 使用 from 和 size 来控制数据的返回数量和偏移量,实现查询的分页。

ES 中,查询主要分为两大类:

  • Leaf Query(叶查询) : 应用于简单的数据查询,直接与文档中的字段进行匹配。例如 matchmulti_matchidstermrange 等查询。

  • Compound Query(复合查询) :结合多个叶查询或其他复合查询,构建复杂的查询逻辑。例如 bool 查询、dis_max 查询等。

2 ES 叶查询

通常情况下,ES 的查询是以 GET 请求的方式,起手式如下所示:

json 复制代码
GET /index_name/_search
{
  "query": {
    // 查询结构
  }
}

一些简单的查询语句构建如下所示:

go 复制代码
`match` 查询是最常用的查询之一,它会对查询字段进行分词并匹配分词后的内容。
{ "query": { "match": { "field_name": "查询参数" } } }

`multi_match` 通常用于指定多个查询字段,查询的内容都是相同的。
{ "query": { "multi_match": { "query": "查询参数", "fields": ["brand","name"] }} }

`term` 查询用于精确匹配字段值,不会进行分词。适合用于关键字 `keyword`类型字段查询。
{ "query": { "term": { "field_name": "查询参数" } } }

`terms` 查询多个精确匹配字段,同 `term` 类似 。
{ "query": { "terms": { "field_name": ["查询参数", "查询参数2"] } } }

`range` 查询用于范围查询,适用于日期、数字等范围类型的数据,查询的条件如(gte lte gt lt 等)
{ "query": { "range": { "age": { "gte": 10, "lte": 20 } } } }
range 查询也可以为如下格式,include_lower 和 include_upper 表示为是否包含上下界
{ "query":{ "range" : { "id" : { "from" : 150, "to" : 180, "include_lower" : true,"include_upper" : true } }}}

`exists` 查询用于检查某个字段是否存在。
{ "query": { "exists": { "field": "字段名称" } } }

`wildcard` 查询支持使用通配符进行模糊匹配。它允许使用 `*` 表示任意数量的字符,`?` 表示单个字符。适用于 `keyword` 类型字段。
{ "query": { "wildcard": { "field_name": "val*" } } }

`prefix` 查询用于查找以某个前缀开始的值,适用于 `keyword` 类型字段。
{ "query": { "fuzzy": { "field_name": { "value": "quikc", "fuzziness": "AUTO" } } } }

`fuzzy` 查询用于查找与指定的词语相近的匹配,支持拼写错误等模糊匹配。可通过 `fuzziness` 参数指定允许的编辑距离。`fuzziness` 可以设置为 `AUTO`、1 或 2,表示允许的编辑距离。
{ "query": { "fuzzy": { "field_name": { "value": "quikc", "fuzziness": "AUTO" } } } }

`ids` 查询用于通过文档的 `_id` 字段查询。
{ "query": { "ids": { "values": ["1", "2", "3"] } } }

3 ES 复合查询

复合查询用于将多个叶查询组合在一起。复合查询可以通过布尔逻辑(例如 ANDOR)或评分(boost)等方式来组合查询。在 bool 查询语句中,是按照子句的顺序进行执行,每个子句中可以放置单独的逻辑。

go 复制代码
`must`: 所有的子查询必须匹配,相当于逻辑 AND。
`should`: 至少有一个子查询匹配,相当于逻辑 OR。
`must_not`: 子查询不匹配,相当于逻辑 NOT。
`filter`: 和 `must` 类似,但不会影响相关性评分。

{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "查询内容" } }
      ],
      "filter": [
        { "term": { "status": "状态信息" } }
      ],
      "must_not": [
        { "range": { "publish_date": { "lt": "2022-01-01" } } }
      ],
      "should": [
        { "match": { "author": "作者信息" } },
        { "match": { "tags": "标签信息" } }
      ],
      "minimum_should_match": 1
    }
  }
}

minimum_should_match 是用来控制查询的经度,如果 bool 查询中仅包含 mustmust_notfilter ,没有 should 子句,那么该值为0,也不起作用。如果包含有 should 子句,那么该值就是用来标识匹配的子句配置,该项配置可以为数字或者百分比,混用(2<-25%, 匹配两个或者25%,两者取最大值)

4 查询实践

4.1 按照分页的方式查询固定的字段并返回
json 复制代码
GET /index_name/_search
{
  "query": {
   "match_all": {}  //查询条件 
  },
  "_source":["id","age","name"], // 查询返回的字段
  "sort": [  // sort 排序,根据 id 进行顺序排列
   { "id": {"order": "asc" } }
   ], 
   "from": 0,
   "size": 500 
}
4.2 将返回的时间戳时间转换为字符串格式
json 复制代码
# 查询单条数据  id = 226, _source 设置为 * 可以用于返回所有字段
GET /index_name/_search
{
  "query":{
    "term":{  "id": "226" }
  },
  "_source":["*"],
  "script_fields": {
    "formDateTime": {
      "script": {
        "lang": "painless",
        "source": """
          ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(doc['pushTime'].value.toInstant().toEpochMilli()), ZoneId.of('UTC+8'));
          DateTimeFormatter formatter = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss.SSS');
          return zdt.format(formatter);
        """
      }
    }
  }
}

这里使用了 script_fields 的 painless 脚本,将文档中的 pushTime 转换为 UTC+8 的时间格式输出, 返回内容多了 formDateTime 字段。如果是低版本的ES,可以将source替换为如下命令,更为简洁。 doc['pushTime'].value.toString('yyyy-MM-dd HH:mm:ss')

4.3 嵌套结构数据查询

对于嵌套的数据结构,需要先进行外层的条件判断,然后在进行内部结构的判断。

json 复制代码
GET /index_name/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {"age": 20}  //最外层内容匹配年龄为 20 
                },
                {
                    "bool": {
                        "must": [
                            {
                                "exists": { "field": "hobbyList" } // 存在 hobbyList list 
                            },
                            {
                                "range": {
                                    "hobbyList.showEndDate": {"gt": '2020-10-10' } // 爱好开始时间过滤
                                }
                            },
                            {
                                "term": { "hobbyList.status": 1 } // 爱好的状态信息过滤
                            }
                        ],
                        "must_not": [
                            {
                                "wildcard": { "hobbyList.name": "足球*" } // 不是足球的爱好
                            }
                        ]
                    }
                }
            ]
        }
    }
}

5 总结

在本文中重要介绍了常用的 DSL 查询语法,以及对应的操作实践,在实际的业务处理和项目运维中可能还会继续有更加复杂的查询需求,会陆续更新到本文中,用于技术的储备和操作的记录。

相关推荐
老纪的技术唠嗑局10 小时前
告别OpenClaw配置丢失——Mindkeeper内测版邀测
大数据·elasticsearch·搜索引擎
Elasticsearch10 小时前
使用 Elasticsearch + Jina embeddings 进行无监督文档聚类
elasticsearch
勇哥的编程江湖13 小时前
flinkcdc streaming 同步数据到es记录过程
大数据·elasticsearch·flink·flinkcdc
曾阿伦13 小时前
Elasticsearch 7.x 常用命令备忘录
大数据·elasticsearch·搜索引擎
斯特凡今天也很帅13 小时前
Elasticsearch数据库专栏(二)DSL语句总结(更新中)
大数据·elasticsearch·搜索引擎
要记得喝水13 小时前
适用于 Git Bash 的脚本,批量提交和推送多个仓库的修改
git·elasticsearch·bash
二十七剑14 小时前
Elasticsearch的索引问题
大数据·elasticsearch·搜索引擎
A__tao1 天前
Elasticsearch Mapping 一键生成 Java 实体类(支持嵌套 + 自动过滤注释)
java·python·elasticsearch
A__tao1 天前
Elasticsearch Mapping 一键生成 Proto 文件(支持嵌套 + 注释过滤)
大数据·elasticsearch·jenkins
Devin~Y1 天前
高并发电商与AI智能客服场景下的Java面试实战:从Spring Boot到RAG与向量数据库落地
java·spring boot·redis·elasticsearch·spring cloud·kafka·rag