作者:来自 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 INsubquery 未来可能会发生变化,也可能在未来版本中被移除。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写代码
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写代码
从内到外阅读。最内层查询查找优先级区域,中间层查询查找这些区域中的客户,而最外层查询则汇总这些客户的收入。每一层都是一个普通的 ES|QL 管道,因此每一层都可以先自行进行过滤、聚合或排序,然后再将整理好的列传递给上一层。
将 WHERE IN 子查询与 AND 和 OR 条件结合使用
由于 IN 子查询是一个布尔条件,因此它可以像其他谓词一样与 AND 和 OR 组合使用。你可以要求同时属于两个独立的集合,也可以接受属于其中任意一个集合:
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 子查询会将三个索引 prod、staging 和 edge 中匹配的主机 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