一个 ES|QL 查询替代两个:`WHERE IN` subquery 取代 Elasticsearch 中的复制粘贴循环

作者:来自 Elastic Fang Xing

ES|QL 的 WHERE 子句现在可以根据另一个 Elasticsearch subquery 的结果进行过滤,而不再需要手动复制静态 ID 列表;同时还支持嵌套 subqueries、NOT IN 以及复合条件。

Elasticsearch Query Language(ES|QL)WHERE 子句现在可以根据另一个查询的结果进行过滤。

如果你之前需要先运行一个查询来查找可疑用户或出现故障的服务,然后复制这些 ID,再将它们粘贴到第二个查询中,那么现在不再需要这样做了。

一个 ES|QL 语句就可以完成整个过程:subquery 从实时数据中动态构建过滤列表,并且每次运行查询时都会保持最新。

该功能在 Elasticsearch 9.5 中以 technical preview 形式提供,并支持:

  • 嵌套 subqueries

  • NOT IN

  • 复合 AND/OR 条件

注意: WHERE IN subquery 未来可能会发生变化,也可能在未来版本中被移除。Elastic 会努力修复相关问题,但 technical preview 功能不受正式 GA 功能所适用的支持 Service Level Agreement(SLA)约束。

静态 ID 列表与使用 ES|QL WHERE 子句进行动态过滤的对比

静态 ID 列表过滤 使用 WHERE IN subquery 进行动态过滤
运行查询 A,找出你关注的 ID 外层查询直接提出主要问题
手动复制这些值 Subquery 根据实时数据构建过滤列表
将这些值粘贴到静态 WHERE IN 列表中 WHERE 字段 IN(subquery)实时应用过滤
使用硬编码列表运行查询 B 结果会随数据保持最新
数据发生变化后,重复整个流程 无需维护复制出来的列表
[图 1:过去的复制粘贴循环被简化为一个动态过滤器。]

WHERE IN subquery 如何取代静态过滤列表

当列表较小且内容固定时,传统的 IN 过滤仍然非常有用:

markdown 复制代码
`

1.  FROM logs-*
2.  | WHERE status_code IN (401, 403, 429)

`AI写代码

但许多实际的调查并不是从一个整齐的列表开始的,而是从一个问题开始的:

  • 哪些用户可疑?
  • 哪些主机产生了大量噪声?
  • 哪些服务出现故障?
  • 哪些账户超过了某个阈值?

这正是 WHERE IN subquery 发挥作用的地方。这个列表由 ES|QL 动态生成,而不再需要手动输入。

WHERE IN subquery 示例:根据可疑用户过滤日志

vbnet 复制代码
`

1.  FROM logs-*
2.  | WHERE user.name IN (
3.      FROM auth-logs-*
4.      | WHERE event.action == "login_failed"
5.      | STATS failed_attempts = COUNT(*) BY user.name
6.      | WHERE failed_attempts >= 10
7.      | KEEP user.name
8.    )

`AI写代码

可以这样理解:

显示那些出现在 "至少有 10 次登录失败的用户列表" 中的用户所产生的日志事件。

外层查询负责提出主要问题,而 subquery 负责动态构建过滤列表,从而省去了复制和粘贴的步骤。

Subquery 可以针对与外层查询不同的索引或索引模式。

不使用 subqueries 进行过滤:手动复制 ID 的工作流

假设你想检查故障最多的服务所产生的流量。首先运行:

sql 复制代码
`

1.  FROM service-logs-*
2.  | WHERE status_code >= 500
3.  | STATS failures = COUNT(*) BY service.name
4.  | SORT failures DESC
5.  | LIMIT 5

`AI写代码

然后,你需要复制这 5 个服务名称,再将它们粘贴到另一个查询中:

sql 复制代码
`

1.  FROM service-logs-*
2.  | WHERE service.name IN ("checkout", "payments", "search", "profile", "orders")

`AI写代码

一次这样做当然没问题,但如果排名前五的服务每小时都会变化,就不太方便了。

使用 WHERE IN subquery 进行动态过滤

less 复制代码
`

1.  FROM service-logs-*
2.  | WHERE service.name IN (
3.      FROM service-logs-*
4.      | WHERE status_code >= 500
5.        AND @timestamp >= now() - 2 days
6.      | STATS failures = COUNT(*) BY service.name
7.      | SORT failures DESC
8.      | LIMIT 5
9.      | KEEP service.name
10.    )
11.    AND status_code >= 500
12.    AND @timestamp >= now() - 2 days
13.  | KEEP @timestamp, service.name, status_code, message

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

Subquery 会从过去两天的数据中找出故障最多的服务,而外层查询则返回这些服务对应的日志事件。一个查询就能呈现完整情况。

在 ES|QL 中使用 NOT IN subqueries 排除值

有时,我们真正关心的问题恰恰是哪些内容不属于某个范围:

markdown 复制代码
`

1.  FROM access-logs-*
2.  | WHERE user.name NOT IN (
3.      FROM known-users
4.      | WHERE user.name IS NOT NULL
5.      | KEEP user.name
6.    )

`AI写代码

这种模式适用于排除检查、差距分析,以及那些需要查找不属于已批准或预期集合的数据的工作流。

ES|QL WHERE 子句中的嵌套 subquery 链

IN subquery 会将字面量值列表替换为括号中的一个查询。内部查询首先执行,并返回单个列,然后外层 WHERE 根据该列进行过滤。

由于这个内部查询本身就是一个完整的 pipeline,因此它可以包含自己的 IN subquery。这样,你就可以表达一连串的查找操作,而不再需要运行三个独立的查询,也不需要进行两轮复制和粘贴。

sql 复制代码
`

1.  FROM orders
2.  | WHERE customer_id IN (
3.      FROM customers
4.      | WHERE region_id IN (
5.          FROM regions
6.          | WHERE tier == "priority"
7.          | KEEP region_id
8.        )
9.      | KEEP customer_id
10.    )
11.  | STATS revenue = SUM(amount) BY customer_id

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

从内到外阅读。最内层查询查找优先级区域,中间层查询查找这些区域中的客户,而最外层查询则汇总这些客户的收入。每一层都是一个普通的 ES|QL 管道,因此每一层都可以先自行进行过滤、聚合或排序,然后再将整理好的列传递给上一层。

将 WHERE IN 子查询与 AND 和 OR 条件结合使用

由于 IN 子查询是一个布尔条件,因此它可以像其他谓词一样与 ANDOR 组合使用。你可以要求同时属于两个独立的集合,也可以接受属于其中任意一个集合:

sql 复制代码
`

1.  FROM orders
2.  | WHERE customer_id IN (FROM vip_customers | KEEP customer_id)
3.    AND product_id IN (FROM discontinued_products | KEEP product_id)
4.  | KEEP order_id, customer_id, product_id, amount

`AI写代码

AND 组合会查找由 VIP 客户下单且产品正在停产的订单。将 AND 替换为 OR,就可以得到满足任一条件的订单。每个子查询都会运行自己的管道,因此两个集合会独立计算,然后再通过布尔运算符进行组合。

将多个索引合并到一个 WHERE IN 子查询中

IN 子查询中的查询是一个完整的管道,因此其中的 FROM 命令可以引用多个子查询。每个分支都会运行自己的管道,而 FROM 命令会将所有分支中的行合并到一个结果集中。KEEP host_id 会选择外层过滤器所需要的唯一列。当你要进行过滤的值分布在多个具有不同架构的索引中时,这种方式非常有用。有关 FROM 命令中的子查询如何处理具有不同架构的索引的更多详细信息,请参阅 三个索引进入一个 FROM 子句:Elasticsearch 中的 ES|QL 子查询

sql 复制代码
`

1.  FROM alerts
2.  | WHERE host_id IN (
3.      FROM
4.        (FROM prod_hosts    | WHERE region == "us-east"),
5.        (FROM staging_hosts | WHERE region == "us-east"),
6.        (FROM edge_hosts    | WHERE region == "us-east")
7.      | KEEP host_id
8.    )
9.  | STATS alert_count = COUNT(*) BY host_id

`AI写代码

IN 子查询会将三个索引 prodstagingedge 中匹配的主机 ID 合并到一个值列表中。外层查询随后会统计这个合并集合中任意主机的告警数量。添加第四个数据源意味着再添加一个分支,而无需修改外层查询。

在每个 FROM 分支中使用 Elasticsearch 子查询

FROM 子查询会为每个索引提供一个独立的分支,每个分支都有自己的 WHERE,而这个 WHERE 可以使用 IN 子查询。这就是如何在多个具有各自架构的索引之间应用相同的动态过滤器。

sql 复制代码
`

1.  FROM
2.    (FROM orders  | WHERE customer_id IN (FROM vip_customers | KEEP customer_id)),
3.    (FROM refunds | WHERE customer_id IN (FROM vip_customers | KEEP customer_id))
4.  | STATS total_events = COUNT(*) BY customer_id

`AI写代码

每个分支都会先将其索引过滤为 VIP 客户,然后再合并两个分支,因此最终的聚合会针对一个统一的行集合执行。

何时使用 ES|QL WHERE IN 子查询

  • 从查找高风险用户、主机、账户或服务开始的调查。

  • 有趣的实体会随时间变化的运维仪表板。

  • Top-N 后续查询,例如查询噪声最大的五个服务产生的事件。

  • 集合比较工作流,尤其是使用 NOT IN 时。

  • 原本需要编写粘合代码,只是为了将一个步骤中的值传递到下一步骤的查询。

WHERE IN 子查询的要求和限制

  • IN 子查询必须恰好返回一列。

  • 在子查询末尾使用 KEEP,这样可以明确比较字段。

  • 确保外层字段和子查询字段具有兼容的类型。

  • 如果子查询使用 SORT,请添加显式的 LIMIT,因为 ES|QL 目前不支持无界 SORT

  • 将其用于成员资格过滤。如果你需要两侧的列,那么 JOIN 可能是更合适的工具。

为什么 ES|QL 动态过滤可以替代手动查询工作流

WHERE IN 子查询将手动工作流转变为声明式工作流。你可以直接在 WHERE 命令中让一个查询为另一个查询构建过滤器,而不必先让 ES|QL 返回一个列表,再将其复制到其他地方,并希望它始终保持最新。

现在,你的 WHERE 子句有了一种更好的方式来处理 根据该查询找到的内容进行过滤

常见问题

子查询有哪些限制?

子查询必须恰好返回一列。在子查询末尾使用 KEEP,以明确指定比较字段。外层查询和子查询中的字段类型必须兼容。如果子查询包含 SORT,请添加显式的 LIMIT

什么时候应该使用 WHERE IN 子查询,而不是 JOIN?

WHERE IN 子查询用于成员资格过滤,也就是说,过滤外层查询中与另一个查询返回的一组值匹配(或不匹配)的行。当你需要在同一个结果行中获取关系两侧的列时,请使用 JOIN

原文:ES|QL WHERE clause: dynamic filtering with IN subqueries | Elasticsearch Labs

相关推荐
Elasticsearch19 小时前
一个字段,覆盖所有模态:Elasticsearch 的 semantic 字段如何自动索引和搜索图像、音频、视频和 PDF
elasticsearch
Elastic 中国社区官方博客20 小时前
使用 Elasticsearch open inference API 对 OpenAI chat completions 进行支持
大数据·elasticsearch·搜索引擎·全文检索·jenkins
Elasticsearch1 天前
jina-reranker-v3.5:通过混合 Attention 和自蒸馏实现更快的 Listwise Reranking
elasticsearch
Elasticsearch1 天前
Elasticsearch 转型为 Agent 基础设施:混合搜索、长期记忆与工程化框架
elasticsearch
Elasticsearch1 天前
在隔离网络和断开连接的环境中部署 Elastic
elasticsearch
Elasticsearch1 天前
从搜索到结账只需 20 行代码:使用 OpenTelemetry 构建四阶段转化漏斗
elasticsearch
Elasticsearch1 天前
你的堆内存图表看不到的隐藏压力:AutoOps 现已监控向量堆外内存
elasticsearch