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

📅 2026/8/10 9:48:45
一个 ES|QL 查询替代两个:`WHERE IN` subquery 取代 Elasticsearch 中的复制粘贴循环
作者来自 Elastic Fang XingES|QL 的WHERE子句现在可以根据另一个 Elasticsearch subquery 的结果进行过滤而不再需要手动复制静态 ID 列表同时还支持嵌套 subqueries、NOT IN以及复合条件。Elasticsearch Query LanguageES|QL 的 WHERE 子句现在可以根据另一个查询的结果进行过滤。如果你之前需要先运行一个查询来查找可疑用户或出现故障的服务然后复制这些 ID再将它们粘贴到第二个查询中那么现在不再需要这样做了。一个 ES|QL 语句就可以完成整个过程subquery 从实时数据中动态构建过滤列表并且每次运行查询时都会保持最新。该功能在 Elasticsearch 9.5 中以 technical preview 形式提供并支持嵌套 subqueriesNOT IN复合AND/OR条件注意WHERE INsubquery 未来可能会发生变化也可能在未来版本中被移除。Elastic 会努力修复相关问题但 technical preview 功能不受正式 GA 功能所适用的支持 Service Level AgreementSLA约束。静态 ID 列表与使用 ES|QLWHERE子句进行动态过滤的对比图 1过去的复制粘贴循环被简化为一个动态过滤器。静态 ID 列表过滤使用WHERE INsubquery 进行动态过滤运行查询 A找出你关注的 ID外层查询直接提出主要问题手动复制这些值Subquery 根据实时数据构建过滤列表将这些值粘贴到静态WHERE IN列表中WHERE字段INsubquery实时应用过滤使用硬编码列表运行查询 B结果会随数据保持最新数据发生变化后重复整个流程无需维护复制出来的列表WHERE INsubquery 如何取代静态过滤列表当列表较小且内容固定时传统的IN过滤仍然非常有用FROM logs-* | WHERE status_code IN (401, 403, 429)但许多实际的调查并不是从一个整齐的列表开始的而是从一个问题开始的哪些用户可疑哪些主机产生了大量噪声哪些服务出现故障哪些账户超过了某个阈值这正是WHERE INsubquery 发挥作用的地方。这个列表由 ES|QL 动态生成而不再需要手动输入。WHERE INsubquery 示例根据可疑用户过滤日志FROM logs-* | WHERE user.name IN ( FROM auth-logs-* | WHERE event.action login_failed | STATS failed_attempts COUNT(*) BY user.name | WHERE failed_attempts 10 | KEEP user.name )可以这样理解显示那些出现在 “至少有 10 次登录失败的用户列表” 中的用户所产生的日志事件。外层查询负责提出主要问题而 subquery 负责动态构建过滤列表从而省去了复制和粘贴的步骤。Subquery 可以针对与外层查询不同的索引或索引模式。不使用 subqueries 进行过滤手动复制 ID 的工作流假设你想检查故障最多的服务所产生的流量。首先运行FROM service-logs-* | WHERE status_code 500 | STATS failures COUNT(*) BY service.name | SORT failures DESC | LIMIT 5然后你需要复制这 5 个服务名称再将它们粘贴到另一个查询中FROM service-logs-* | WHERE service.name IN (checkout, payments, search, profile, orders)一次这样做当然没问题但如果排名前五的服务每小时都会变化就不太方便了。使用WHERE INsubquery 进行动态过滤FROM service-logs-* | WHERE service.name IN ( FROM service-logs-* | WHERE status_code 500 AND timestamp now() - 2 days | STATS failures COUNT(*) BY service.name | SORT failures DESC | LIMIT 5 | KEEP service.name ) AND status_code 500 AND timestamp now() - 2 days | KEEP timestamp, service.name, status_code, messageSubquery 会从过去两天的数据中找出故障最多的服务而外层查询则返回这些服务对应的日志事件。一个查询就能呈现完整情况。在 ES|QL 中使用NOT INsubqueries 排除值有时我们真正关心的问题恰恰是哪些内容不属于某个范围FROM access-logs-* | WHERE user.name NOT IN ( FROM known-users | WHERE user.name IS NOT NULL | KEEP user.name )这种模式适用于排除检查、差距分析以及那些需要查找不属于已批准或预期集合的数据的工作流。ES|QLWHERE子句中的嵌套 subquery 链INsubquery 会将字面量值列表替换为括号中的一个查询。内部查询首先执行并返回单个列然后外层WHERE根据该列进行过滤。由于这个内部查询本身就是一个完整的 pipeline因此它可以包含自己的INsubquery。这样你就可以表达一连串的查找操作而不再需要运行三个独立的查询也不需要进行两轮复制和粘贴。FROM orders | WHERE customer_id IN ( FROM customers | WHERE region_id IN ( FROM regions | WHERE tier priority | KEEP region_id ) | KEEP customer_id ) | STATS revenue SUM(amount) BY customer_id从内到外阅读。最内层查询查找优先级区域中间层查询查找这些区域中的客户而最外层查询则汇总这些客户的收入。每一层都是一个普通的 ES|QL 管道因此每一层都可以先自行进行过滤、聚合或排序然后再将整理好的列传递给上一层。将 WHERE IN 子查询与 AND 和 OR 条件结合使用由于IN子查询是一个布尔条件因此它可以像其他谓词一样与AND和OR组合使用。你可以要求同时属于两个独立的集合也可以接受属于其中任意一个集合FROM orders | WHERE customer_id IN (FROM vip_customers | KEEP customer_id) AND product_id IN (FROM discontinued_products | KEEP product_id) | KEEP order_id, customer_id, product_id, amountAND组合会查找由 VIP 客户下单且产品正在停产的订单。将AND替换为OR就可以得到满足任一条件的订单。每个子查询都会运行自己的管道因此两个集合会独立计算然后再通过布尔运算符进行组合。将多个索引合并到一个 WHERE IN 子查询中IN子查询中的查询是一个完整的管道因此其中的FROM命令可以引用多个子查询。每个分支都会运行自己的管道而FROM命令会将所有分支中的行合并到一个结果集中。KEEP host_id会选择外层过滤器所需要的唯一列。当你要进行过滤的值分布在多个具有不同架构的索引中时这种方式非常有用。有关FROM命令中的子查询如何处理具有不同架构的索引的更多详细信息请参阅 三个索引进入一个 FROM 子句Elasticsearch 中的 ES|QL 子查询。FROM alerts | WHERE host_id IN ( FROM (FROM prod_hosts | WHERE region us-east), (FROM staging_hosts | WHERE region us-east), (FROM edge_hosts | WHERE region us-east) | KEEP host_id ) | STATS alert_count COUNT(*) BY host_idIN子查询会将三个索引prod、staging和edge中匹配的主机 ID 合并到一个值列表中。外层查询随后会统计这个合并集合中任意主机的告警数量。添加第四个数据源意味着再添加一个分支而无需修改外层查询。在每个 FROM 分支中使用 Elasticsearch 子查询FROM子查询会为每个索引提供一个独立的分支每个分支都有自己的WHERE而这个WHERE可以使用IN子查询。这就是如何在多个具有各自架构的索引之间应用相同的动态过滤器。FROM (FROM orders | WHERE customer_id IN (FROM vip_customers | KEEP customer_id)), (FROM refunds | WHERE customer_id IN (FROM vip_customers | KEEP customer_id)) | STATS total_events COUNT(*) BY customer_id每个分支都会先将其索引过滤为 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