正确做法是分子用sum(case when...then 1 else 0 end) over(partition by),分母用count(*) over(partition by),相除时显式转浮点并用nullif防除零;禁用order by以免变成累计窗口,避免case缺else或count()替代sum导致null漏计。

窗口函数里怎么写条件占比的 COUNT 或 SUM
直接用 COUNT(*) 或 SUM(1) 做分母会出错——窗口函数不支持在 OVER() 里嵌套聚合,更不能直接除。正确做法是把分子和分母都拆成独立的窗口计算:分子用条件聚合(SUM(CASE WHEN ... THEN 1 ELSE 0 END)),分母用无条件计数(COUNT(*) OVER(...)),再相除。
常见错误是写成 COUNT(CASE WHEN cond THEN 1 END) OVER(...),这会导致 NULL 被忽略,结果偏小;必须用 SUM + CASE 确保所有行参与计算。
-
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) OVER (PARTITION BY user_id)—— 分子,统计每个用户成功次数 -
COUNT(*) OVER (PARTITION BY user_id)—— 分母,每个用户的总记录数 - 两者相除后建议显式转为
DECIMAL或FLOAT,避免整数除法截断(如 PostgreSQL 用::DECIMAL,MySQL 用CAST(... AS DECIMAL))
OVER(PARTITION BY ... ORDER BY ...) 会影响占比结果吗?
会影响。如果加了 ORDER BY,窗口变成“从第一行到当前行”的累积窗口,COUNT(*) OVER(... ORDER BY ts) 返回的是当前行及之前行的数量,不是全局分组总数。算占比时分母就错了。
除非你明确要算“截止当前的累计占比”(比如实时转化率看板),否则绝大多数场景下分母必须是静态分组总数,OVER 子句里只保留 PARTITION BY,不要带 ORDER BY。
- 要静态占比 →
COUNT(*) OVER (PARTITION BY category) - 要累计占比 →
COUNT(*) OVER (PARTITION BY category ORDER BY created_at),但分子也得同步用累积形式 - MySQL 8.0+ 和 PostgreSQL 支持
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING显式声明全窗口,但纯PARTITION BY已隐含该语义,不必冗余写
不同数据库对空值和除零的处理差异
分母为 0 时行为不一致:PostgreSQL 直接报错 division by zero;MySQL 返回 NULL;SQL Server 报错但可用 NULLIF 拦截。必须主动防御。
空值也会污染分子:CASE WHEN x > 0 THEN 1 遇到 x IS NULL 会进 ELSE 0 分支,没问题;但若写成 CASE WHEN x > 0 THEN 1 END(缺 ELSE),NULL 行的分子就是 NULL,整个占比变 NULL。
- 统一用
NULLIF(denominator, 0)包裹分母,避免除零(PostgreSQL/SQL Server/MySQL 均支持) - 分子的
CASE必须带ELSE 0,确保每行都有确定值 - 示例:
SUM(CASE WHEN score >= 60 THEN 1 ELSE 0 END) OVER (PARTITION BY class_id) * 1.0 / NULLIF(COUNT(*) OVER (PARTITION BY class_id), 0)
为什么用 AVG(CASE WHEN ... THEN 1.0 ELSE 0.0 END) 更简洁?
因为 AVG 本质是 SUM / COUNT,且自动跳过 NULL,只要把条件映射成 1.0/0.0,就能一步到位算占比。比手写 SUM/COUNT 少写一半代码,还天然规避除零(AVG 在空组返回 NULL,而非报错)。
- 等价写法:
AVG(CASE WHEN paid = true THEN 1.0 ELSE 0.0 END) OVER (PARTITION BY region) - 注意必须用
1.0而非1,否则某些数据库(如 SQLite)仍做整数除法 - 这个技巧在 BigQuery、Redshift、Snowflake 全部适用,是跨平台最稳的写法
真正容易被忽略的点是:窗口函数的执行顺序在 WHERE 之后、GROUP BY 之前,所以过滤条件要放在 WHERE 而不是塞进 CASE 里——否则无效行仍占分母,拉低占比。











