窗口函数中嵌套case when不会直接报错,但必须将其置于sum()、count()等聚合函数内部作为输入表达式,不可单独写在over()外;需保证分支返回类型一致,建议显式指定else子句。

窗口函数里嵌套CASE WHEN会报错吗?
不会直接报错,但必须确保CASE WHEN出现在聚合函数内部或作为窗口函数的输入表达式,不能单独写在OVER()外面。常见错误是把CASE WHEN当列别名用,比如:SELECT CASE WHEN status='paid' THEN amount END OVER (PARTITION BY user_id ORDER BY time)——这语法非法,OVER只能修饰聚合或排序类函数。
-
CASE WHEN必须包裹在SUM()、COUNT()、AVG()等窗口函数里
- 条件分支结果类型要一致(比如都返回数值,或都返回
NULL)
-
ELSE建议显式写成ELSE 0或ELSE NULL,避免隐式转换引发意外结果
按状态分组累计支付金额怎么写?
典型场景:用户每笔订单有status('paid'/'failed'/'pending'),想看每个用户“已支付”金额的逐行累计值。
SELECT
user_id,
time,
status,
amount,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END)
OVER (PARTITION BY user_id ORDER BY time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS paid_cumsum
FROM orders;
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是累计和的默认框架,不写也生效,但显式写出更安全
- 如果
time有重复,建议加id作为第二排序字段,否则相同时间的行窗口顺序不确定
-
ELSE 0比ELSE NULL更适合累计场景,避免SUM遇到NULL时整行变NULL
统计每个用户首次付费后的行为留存率
难点在于:先定位“首次付费时间”,再判断后续行为是否发生在该时间之后。不能只靠WHERE过滤,得用窗口函数+CASE WHEN联动。
WITH first_paid AS (
SELECT
user_id,
MIN(CASE WHEN status = 'paid' THEN time END) AS first_paid_time
FROM orders
GROUP BY user_id
)
SELECT
o.user_id,
o.time,
o.status,
CASE
WHEN o.time >= fp.first_paid_time AND o.status = 'view' THEN 1
ELSE 0
END AS is_retained_view,
SUM(CASE WHEN o.time >= fp.first_paid_time AND o.status = 'view' THEN 1 ELSE 0 END)
OVER (PARTITION BY o.user_id ORDER BY o.time) AS retained_view_count
FROM orders o
JOIN first_paid fp ON o.user_id = fp.user_id;
- 关联子查询或CTE先算出
first_paid_time,否则无法在窗口中引用“当前用户首次付费时间”
-
CASE WHEN里用>=而非>,包含首次付费当天的其他行为
- 留存统计依赖时间顺序,
ORDER BY o.time不可省略,否则SUM() OVER结果无意义
为什么COUNT(CASE WHEN ...) OVER的结果总是1?
这是最常踩的坑:COUNT()只统计非NULL值,而CASE WHEN没匹配到时默认返回NULL,所以COUNT(CASE WHEN cond THEN 1 END)等价于“满足条件的行数”,但若漏写ELSE,不满足条件的行就贡献NULL,不影响计数——看起来像每行都算1,其实是窗口内所有满足条件的行被累计了。
- 想统计“本行是否满足条件”,用
SUM(CASE WHEN ... THEN 1 ELSE 0 END)更直观
- 想统计“窗口内满足条件的记录数”,用
COUNT(CASE WHEN ... THEN 1 END)正确,但要注意COUNT忽略NULL的特性
- 不要混用
COUNT和SUM逻辑,比如COUNT(CASE WHEN ... THEN amount END)会统计amount非空且条件成立的行数,不是金额总和
CASE WHEN必须包裹在SUM()、COUNT()、AVG()等窗口函数里NULL)ELSE建议显式写成ELSE 0或ELSE NULL,避免隐式转换引发意外结果status('paid'/'failed'/'pending'),想看每个用户“已支付”金额的逐行累计值。
SELECT
user_id,
time,
status,
amount,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END)
OVER (PARTITION BY user_id ORDER BY time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS paid_cumsum
FROM orders;
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是累计和的默认框架,不写也生效,但显式写出更安全 - 如果
time有重复,建议加id作为第二排序字段,否则相同时间的行窗口顺序不确定 -
ELSE 0比ELSE NULL更适合累计场景,避免SUM遇到NULL时整行变NULL
统计每个用户首次付费后的行为留存率
难点在于:先定位“首次付费时间”,再判断后续行为是否发生在该时间之后。不能只靠WHERE过滤,得用窗口函数+CASE WHEN联动。
WITH first_paid AS (
SELECT
user_id,
MIN(CASE WHEN status = 'paid' THEN time END) AS first_paid_time
FROM orders
GROUP BY user_id
)
SELECT
o.user_id,
o.time,
o.status,
CASE
WHEN o.time >= fp.first_paid_time AND o.status = 'view' THEN 1
ELSE 0
END AS is_retained_view,
SUM(CASE WHEN o.time >= fp.first_paid_time AND o.status = 'view' THEN 1 ELSE 0 END)
OVER (PARTITION BY o.user_id ORDER BY o.time) AS retained_view_count
FROM orders o
JOIN first_paid fp ON o.user_id = fp.user_id;
- 关联子查询或CTE先算出
first_paid_time,否则无法在窗口中引用“当前用户首次付费时间”
-
CASE WHEN里用>=而非>,包含首次付费当天的其他行为
- 留存统计依赖时间顺序,
ORDER BY o.time不可省略,否则SUM() OVER结果无意义
为什么COUNT(CASE WHEN ...) OVER的结果总是1?
这是最常踩的坑:COUNT()只统计非NULL值,而CASE WHEN没匹配到时默认返回NULL,所以COUNT(CASE WHEN cond THEN 1 END)等价于“满足条件的行数”,但若漏写ELSE,不满足条件的行就贡献NULL,不影响计数——看起来像每行都算1,其实是窗口内所有满足条件的行被累计了。
- 想统计“本行是否满足条件”,用
SUM(CASE WHEN ... THEN 1 ELSE 0 END)更直观
- 想统计“窗口内满足条件的记录数”,用
COUNT(CASE WHEN ... THEN 1 END)正确,但要注意COUNT忽略NULL的特性
- 不要混用
COUNT和SUM逻辑,比如COUNT(CASE WHEN ... THEN amount END)会统计amount非空且条件成立的行数,不是金额总和
first_paid_time,否则无法在窗口中引用“当前用户首次付费时间”CASE WHEN里用>=而非>,包含首次付费当天的其他行为ORDER BY o.time不可省略,否则SUM() OVER结果无意义COUNT()只统计非NULL值,而CASE WHEN没匹配到时默认返回NULL,所以COUNT(CASE WHEN cond THEN 1 END)等价于“满足条件的行数”,但若漏写ELSE,不满足条件的行就贡献NULL,不影响计数——看起来像每行都算1,其实是窗口内所有满足条件的行被累计了。
- 想统计“本行是否满足条件”,用
SUM(CASE WHEN ... THEN 1 ELSE 0 END)更直观 - 想统计“窗口内满足条件的记录数”,用
COUNT(CASE WHEN ... THEN 1 END)正确,但要注意COUNT忽略NULL的特性 - 不要混用
COUNT和SUM逻辑,比如COUNT(CASE WHEN ... THEN amount END)会统计amount非空且条件成立的行数,不是金额总和
窗口函数和CASE WHEN组合的关键,是把条件逻辑收束进聚合函数内部,而不是试图用CASE去控制窗口本身的行为。框架定义、排序稳定性、NULL处理,这三个地方出问题,结果就容易偏离预期。











