SQL 如何用 QUALIFY 子句(Snowflake/Databricks)简化窗口过滤

7次阅读

QUALIFY 比 WHERE + 子查询更直接,因其专为过滤窗口函数结果设计,在逻辑执行顺序中紧接窗口计算之后、投影之前,避免嵌套子查询和物化开销,并解决 WHERE 中禁用窗口函数的语法限制。

SQL 如何用 QUALIFY 子句(Snowflake/Databricks)简化窗口过滤

QUALIFY 为什么比 WHERE + 子查询更直接

因为 QUALIFY 是专为过滤窗口函数结果设计的语法糖,它在逻辑执行顺序中紧接在窗口函数计算之后、最终投影之前,避免了嵌套子查询。你不用再写一层 select * FROM (SELECT ..., ROW_NUMBER() OVER (...) AS rn FROM ...) WHERE rn = 1 —— 这种写法不仅冗长,还容易让执行计划多出一次物化(尤其在大表上影响明显)。

常见错误现象:在 WHERE 中直接引用窗口函数,比如 WHERE ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts DESC) = 1,会报错 sql compilation Error: window function not allowed in WHERE clause。这就是必须用 QUALIFY 的根本原因。

QUALIFY 在 Snowflake 和 Databricks 中的写法差异

两者都支持 QUALIFY,但兼容性细节不同:

  • Snowflake 从 5.3 版本起完全支持,可与任意窗口函数组合,包括 RANK()ROW_NUMBER()LAG()AVG() OVER ()
  • Databricks(spark SQL)从 Runtime 11.0+ / Spark 3.3+ 开始支持,但不支持在 QUALIFY 中使用非确定性函数(如 NOW()RAND()),否则报错 Non-deterministic expression is not allowed
  • 两者都不允许在 QUALIFY 中引用外部查询的列(即不能跨层引用),只能依赖当前 SELECT 列或窗口表达式本身

示例(取每个用户的最新订单):

SELECT user_id, order_id, ts FROM orders QUALIFY ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts DESC) = 1;

QUALIFY 和 HAVING 的混淆点在哪

HAVING 是聚合后的过滤,作用于 GROUP BY 分组结果;而 QUALIFY 是窗口计算后的过滤,作用于每行(即使没分组)。它们解决的是完全不同的问题:

  • 想对每个用户取 top 3 订单?用 QUALIFY ROW_NUMBER() OVER (...)
  • 想查平均订单金额 > 100 的用户?用 GROUP BY user_id HAVING AVG(amount) > 100
  • 如果混用:在含 GROUP BY 的查询里加 QUALIFY,窗口函数仍按行计算(不受 GROUP BY 影响),除非窗口定义里显式用了 PARTITION BY 匹配分组字段

性能提示:在 Snowflake 中,QUALIFY 通常能被优化器下推到扫描阶段;但在 Databricks 中,若窗口函数涉及复杂表达式,可能触发额外 shuffle。

容易忽略的边界情况

QUALIFY 不会改变原始行数的语义理解——它只是“筛掉”不满足条件的行,不是“聚合”。这意味着:

  • 空值处理要小心:QUALIFY COUNT(*) OVER (PARTITION BY x) > 0x IS NULL 的行是否生效,取决于数据库对 NULL 分组的默认行为(Snowflake 默认把 NULL 当独立分组,Databricks 也是)
  • 不能和 ORDER BY 同时出现在同一级查询末尾(语法错误),需把 ORDER BY 放在 QUALIFY 之后,或包一层外层查询
  • 在 Databricks 中,如果窗口函数用了 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 这类帧定义,QUALIFY 仍可用,但需确保帧内逻辑与过滤意图一致(例如用累计和过滤,要注意是“首次超过阈值”还是“最终值”)

最常被跳过的一步:在迁移旧代码时,只改 WHEREQUALIFY,却忘了删掉原窗口别名——比如 SELECT ..., ROW_NUMBER() OVER (...) AS rn QUALIFY rn = 1 是合法的,但如果你没在 SELECT 列表里显式写出 rn,某些版本 Databricks 会报 column 'rn' does not exist。所以要么 SELECT 出该别名,要么直接写完整表达式。

text=ZqhQzanResources