为什么 QUALIFY 不只是少一层子查询
提到
QUALIFY
但在真实查询里,更实际的问题往往不是能不能少套一层,而是:当一条 SQL 已经同时出现窗口函数、alias、CTE、聚合、排序时,
QUALIFY
QUALIFY
-
计算窗口值;
-
按窗口值筛选结果。
如果这两件事被拆到子查询和外层
WHERE
-
分区规则是什么;
-
排序规则是什么;
-
最后保留哪些行。
换句话说,
QUALIFY
场景一:每组取最新一条,把窗口计算和过滤放在一起
这是
QUALIFY
比如从事件表里取每个用户最近一次登录记录,传统写法通常需要先在子查询里计算
row_number()
不推荐的写法
SELECT *
FROM (
SELECT
user_id,
event_time,
row_number() OVER (
PARTITION BY user_id
ORDER BY event_time DESC
) AS rn
FROM events
WHERE event_type = 'login'
) t
WHERE rn = 1;
这段 SQL 当然能看懂,但需要跨两层才能把意图拼完整:
-
内层负责计算排名;
-
外层负责保留第一条;
-
读者需要把
映射回内层的窗口函数定义。WHERE rn = 1
更推荐的写法
SELECT
user_id,
event_time,
row_number() OVER (
PARTITION BY user_id
ORDER BY event_time DESC
) AS rn
FROM events
WHERE event_type = 'login'
QUALIFY rn = 1;
这里的阅读顺序更自然:
-
先保留登录事件;
-
再在每个用户内按时间倒序排名;
-
最后保留排在最前面的一条。
"怎么算排名"和"保留哪一名"被放在同一个视野里,SQL 的意图会更直接。
场景二:窗口表达式起 alias,让业务意图更明显
如果窗口表达式稍微复杂一点,直接在
QUALIFY
不推荐的写法
SELECT
user_id,
session_id,
amount
FROM payments
QUALIFY row_number() OVER (
PARTITION BY user_id
ORDER BY amount DESC, session_id
) = 1;
这段 SQL 的问题不是不能执行,而是窗口规则和过滤条件被揉在一起,读者需要先解析完整表达式,才能理解最终筛选逻辑。
更推荐的写法
SELECT
user_id,
session_id,
amount,
row_number() OVER (
PARTITION BY user_id
ORDER BY amount DESC, session_id
) AS top_payment_rank
FROM payments
QUALIFY top_payment_rank = 1;
alias 不只是为了复用表达式,也是在解释业务含义。
当
OVER (...)
-
latest_row
-
dept_rank
-
dedup_rank
-
top_payment_rank
好的 alias 能减少注释需求,让 SQL 本身更接近业务表达。
场景三:区分 Top 1 和并列 Top N
很多可读性问题,实际上来自函数选型不清楚。
如果目标是"每组只保留一条",通常使用
row_number()
SELECT
user_id,
event_time,
row_number() OVER (
PARTITION BY user_id
ORDER BY event_time DESC, event_id DESC
) AS latest_row
FROM events
QUALIFY latest_row = 1;
这里需要注意:
row_number() ... QUALIFY latest_row = 1
如果
ORDER BY
ORDER BY
如果目标是"保留并列前几名",通常使用
rank()
dense_rank()
SELECT
department,
employee_id,
salary,
rank() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS salary_rank
FROM employees
QUALIFY salary_rank <= 3;
这时
QUALIFY
rank()
dense_rank()
row_number()
场景四:让 WHERE、HAVING、QUALIFY 各自只做自己的事
复杂查询最怕的是每个子句里都掺一点别的阶段语义。
更清楚的分工通常是:
-
:过滤原始行;WHERE
-
:过滤聚合结果;HAVING
-
:过滤窗口结果。QUALIFY
例如:
SELECT
user_id,
event_time,
row_number() OVER (
PARTITION BY user_id
ORDER BY event_time DESC
) AS rn
FROM events
WHERE event_type = 'login'
QUALIFY rn = 1;
这段 SQL 的语义分层很明确:
-
说明窗口计算基于登录事件;WHERE event_type = 'login'
-
说明在每个用户内排序;row_number()
-
说明最终只保留每个用户的最新一条。QUALIFY rn = 1
如果把本该在
WHERE
场景五:CTE 负责切分语义阶段,不要只为了过滤窗口值而包一层
CTE 在复杂查询里当然仍然有价值,但前提是每一层都要有明确职责。
更推荐的切法通常是:
-
前一层 CTE:准备基础数据、做预聚合、清洗字段;
-
当前层:做窗口计算;
-
当前层直接
。QUALIFY
例如,先聚合出每个店铺每天的销售额,再找出每个店铺销售额最高的一天:
WITH daily_sales AS (
SELECT
shop_id,
sale_date,
sum(amount) AS daily_amount
FROM sales
GROUP BY shop_id, sale_date
)
SELECT
shop_id,
sale_date,
daily_amount,
row_number() OVER (
PARTITION BY shop_id
ORDER BY daily_amount DESC, sale_date DESC
) AS sales_rank
FROM daily_sales
QUALIFY sales_rank = 1;
这里 CTE 的职责很清楚:它是为了先得到日粒度结果,而不是为了给外层
WHERE rn = 1
这和"先套一个子查询,只是为了外层写窗口过滤条件"是两种完全不同的层次。
场景六:窗口规则复用时,用 named window 降低噪音
如果同一条查询里有多个窗口函数共享相同的
PARTITION BY
ORDER BY
OVER (...)
SELECT
user_id,
event_time,
row_number() OVER w AS rn,
lag(event_time) OVER w AS prev_event_time
FROM events
WINDOW w AS (
PARTITION BY user_id
ORDER BY event_time DESC
)
QUALIFY rn = 1;
这里的收益不是"少写几行",而是:
-
一眼就能看出这些窗口函数共用同一套窗口定义;
-
窗口定义只需要审一遍;
-
更像是在直接消费这套窗口规则。QUALIFY rn = 1
当窗口逻辑开始复用时,named window 能显著减少重复噪音。
什么时候不要把逻辑塞进 QUALIFY
QUALIFY
下面几类内容,通常仍然不适合塞进
QUALIFY
-
复杂业务过滤条件;
-
多段聚合逻辑;
-
大段重复窗口表达式;
-
和窗口无关的普通条件判断。
一个常见坏味道是:
QUALIFY
更好的做法是:
-
普通过滤前移到
;WHERE -
聚合过滤放在
;HAVING -
让
尽量只承担"按窗口结果筛选"这一件事。QUALIFY
判断一条查询是否适合
QUALIFY
如果在脑子里描述这条查询时,会自然说出:
-
"先在每组里排一下,再保留第一条";
-
"先求一个排名,再保留前 3 名";
-
"先算窗口值,再按窗口值筛"。
那这通常就是
QUALIFY
如果更接近:
-
"先过滤原始数据";
-
"先聚合,再过滤聚合结果"。
那更可能属于
WHERE
HAVING
在 Databend 中使用 QUALIFY 的意义
在 Databend 中,
QUALIFY
对于熟悉 Snowflake / BigQuery 查询风格的数据工程师来说,
QUALIFY
这也符合 Databend 作为现代云原生数仓的设计方向:支持熟悉、开放、可组合的 SQL 能力,让复杂分析逻辑既能高效执行,也能被团队长期维护。
尤其是在事件日志、用户行为、半结构化数据和 AI / agent trace 等分析场景中,经常会遇到"先排序、再去重""先排名、再取 Top N""先计算窗口值、再保留关键行"的查询模式。
QUALIFY
总结
QUALIFY
先计算窗口值,再按窗口值筛选。
当查询的核心语义是每组取最新一条、保留 Top N、去重、排名过滤或状态提取时,
QUALIFY
但它也不应该变成所有复杂逻辑的容器。保持
WHERE
HAVING
QUALIFY
订阅我们的新闻简报
及时了解功能发布、产品规划、支持服务和云服务的最新信息!






