国破山河在,城春草木深。
1 前言
Elasticsearch
是一个开源的分布式搜索和分析引擎,其强大的查询能力支撑了复杂的业务查询。其领域特定语言DSL(Domain Specific Language
)是通过 JSON
构建的,非常灵活的支持多种方式的查询。本文将结合业务实践,总结使用 ES
查询 DSL
的结构和常用的查询方法,巩固使用 ES
查询数据的基础知识。
2 DSL 介绍
DSL 常用的搜索操作包括:
- 全文搜索 对文本字段执行搜索,支持短语搜索和模糊匹配。
- 结构化搜索 对结构化的数据字段进行搜索,比如日期、数字、枚举等。
- 复合查询 多种查询条件的组合,比如 bool 查询,通常用来执行复杂的搜索条件。
- 数据聚合 对数据进行聚合查询,用于执行统计信息。
- 结果排序 对查询的结果根据一个或者多个字段进行排序后展示。
- 数据分页 使用 from 和 size 来控制数据的返回数量和偏移量,实现查询的分页。
在 ES
中,查询主要分为两大类:
-
Leaf Query(叶查询) : 应用于简单的数据查询,直接与文档中的字段进行匹配。例如
match
、multi_match
、ids
、term
、range
等查询。 -
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 复合查询
复合查询用于将多个叶查询组合在一起。复合查询可以通过布尔逻辑(例如 AND
、OR
)或评分(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
查询中仅包含 must
、must_not
和 filter
,没有 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
查询语法,以及对应的操作实践,在实际的业务处理和项目运维中可能还会继续有更加复杂的查询需求,会陆续更新到本文中,用于技术的储备和操作的记录。