case when 是oracle中多条件聚合的首选,支持范围判断和模糊匹配,而decode仅限等值匹配;配合rollup、grouping、窗口函数及null/精度处理可实现健壮分摊计算。

用 CASE WHEN 做多条件聚合,别硬套 DECODE
Oracle 里实现“同一字段按不同条件分别求和/计数”,CASE WHEN 是首选,不是 DECODE。虽然 DECODE 看起来简洁,但它只支持等值匹配,没法写 >、LIKE、IS NULL 这类逻辑,一遇到范围判断或模糊匹配就卡住。
常见错误是把 DECODE 当万能开关,比如想统计“工资 > 5000 的人数”,写成 DECODE(SIGN(salary - 5000), 1, 1, 0) ——这不仅难读,还绕远路。直接用 CASE WHEN salary > 5000 THEN 1 ELSE 0 END 更直白、可维护性高,且 Oracle 优化器对 CASE 的识别更成熟。
-
COUNT(CASE WHEN status = 'Completed' THEN 1 END):显式返回1或隐式NULL,COUNT自动忽略NULL,安全 -
SUM(CASE WHEN category = 'A' THEN amount ELSE 0 END):用0替代NULL,避免聚合结果被NULL污染 - 所有
CASE分支的返回类型必须一致,比如都返回NUMBER或都返回VARCHAR2,否则报ORA-00932
ROLLUP 和 GROUPING 配合做分摊底表,不是加个 GROUP BY 就完事
要做“部门级汇总 + 公司级总计 + 各部门内岗位小计”这种嵌套分摊,光靠 GROUP BY dept, job 不够,得用 GROUP BY ROLLUP(dept, job)。但关键在后续处理:Oracle 会在汇总行里把分组字段填成 NULL,而你得靠 GROUPING() 函数区分这是“真 NULL”还是“汇总占位符”。
比如 GROUPING(dept) 返回 1 表示当前行是跨部门汇总(即 dept 字段为汇总占位),返回 0 才是真实数据。不加这个判断,报表里就会混进一堆无法解释的 NULL 部门名。
-
SELECT dept, job, SUM(salary), GROUPING(dept), GROUPING(job) FROM emp GROUP BY ROLLUP(dept, job)是调试起点 - 正式输出建议包装一层:
CASE WHEN GROUPING(dept) = 1 THEN 'TOTAL' ELSE dept END AS dept_label -
ROLLUP(a,b,c)会生成 (a,b,c)、(a,b)、(a)、() 四层,顺序不能错;要跳过某层(比如不要部门级小计只要公司总计),得换CUBE或手动UNION ALL
OVER(PARTITION BY ...) 做分摊比例计算,ORDER BY 是雷区
分摊常需“某部门销售额占全公司比例”,这时用窗口函数比自连接干净得多:SUM(amount) OVER() / SUM(amount) OVER(PARTITION BY dept)。但注意:OVER() 里如果漏写 PARTITION BY,默认就是全表一个分区,结果全是 100%;如果误加了 ORDER BY,SUM 就变成累计和,比例就崩了。
典型错误是看到文档说“ORDER BY 启用累计”,就下意识加上去,结果发现分摊系数越算越大。其实只要分母是全局或分区总和,就不该有 ORDER BY ——除非你真要算“到当前行为止的占比”。
- 分摊分子:用
SUM(amount) OVER(PARTITION BY dept) - 分摊分母:用
SUM(amount) OVER()(全表)或SUM(amount) OVER(PARTITION BY region)(按区域) - 一旦加了
ORDER BY,必须显式写ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才能恢复全分区求和语义
复杂分摊结果要落地,别让 NULL 和精度毁掉一致性
最后一步常被忽略:分摊计算后,数值可能因四舍五入导致总和对不上(比如 100 分摊成 33+33+34=100,但浮点运算后变成 33.33+33.33+33.33=99.99)。Oracle 默认 NUMBER 类型不自动补差,得人工干预。
更隐蔽的问题是 NULL 参与计算:比如某个部门没数据,SUM(amount) OVER(PARTITION BY dept) 返回 NULL,再除以总数就整个变 NULL。必须用 NVL(..., 0) 或 COALESCE(..., 0) 截断。
- 比例计算后加
ROUND(..., 2)控制小数位,但记得最后补差逻辑要单独写(比如取 TOP 1 行把余数塞进去) - 涉及金额分摊,优先用
NUMBER(18,2)显式声明精度,别依赖默认 NUMBER - 所有参与分摊的字段,提前用
NVL(amount, 0)处理空值,否则NULL * 0.3还是NULL











