用视图封装 GROUP BY ROLLUP 最稳妥,但必须配合 GROUPING() 处理 NULL 占位符,否则 WHERE city IS NOT NULL 会误删 region 小计行;需显式暴露 is_region_total 等标志位,结合 CASE WHEN 输出“北京市小计”等可读标签,并严格按列序判断 GROUPING()。

直接说结论:用视图封装 GROUP BY ROLLUP 是最稳妥的简化方式,但必须配合 GROUPING() 处理 NULL,否则视图一用就错。
为什么不能直接在视图里写 GROUP BY ROLLUP 而不加判断
ROLLUP 生成的 NULL 不是缺失值,而是层级占位符。比如视图定义为:
CREATE VIEW sales_rollup AS SELECT region, city, SUM(amount) AS total FROM sales GROUP BY ROLLUP (region, city);
调用时若写 WHERE city IS NOT NULL,会把所有“region 小计”行(city 列为 NULL)全过滤掉——这不是漏数据,是逻辑误删。
实操建议:
- 视图里必须显式暴露
GROUPING(region)和GROUPING(city)字段,命名如is_region_total、is_city_total - 前端或下游查询靠这些标志位做条件,而不是直接判
IS NULL - MySQL 8.0+ 和 SQL Server 支持,但 SQLite 视图里不能用 ROLLUP,得换方案
GROUPING() 怎么和 CASE WHEN 配合输出可读标签
报表要显示“北京市小计”“华北区总计”,不能只靠 NULL。得用 GROUPING() 做开关:
SELECT
CASE
WHEN GROUPING(region) = 1 THEN '全国总计'
WHEN GROUPING(city) = 1 THEN CONCAT(region, '小计')
ELSE city
END AS display_name,
SUM(amount) AS total
FROM sales
GROUP BY ROLLUP (region, city);
注意点:
-
GROUPING(region)为 1 表示当前行 region 维度被折叠(即 region 级汇总或更高),不是 region 字段真为空 - 顺序必须严格按 ROLLUP 列序:先判最左字段(最高层),再判右字段(低层),否则逻辑错乱
- PostgreSQL 中函数名是
GROUPING(),Oracle 也是,SQL Server 同名;别写成IS_GROUPING()或其他变体
视图里能用窗口函数补占比、排名吗
不能直接套在 ROLLUP 结果上——窗口函数不改变行数,而 ROLLUP 输出的是聚合后的新结果集,二者语义冲突。
正确做法是两层包装:
- 第一层视图:只做
GROUP BY ROLLUP,输出带GROUPING()标志的聚合结果 - 第二层查询(或另一个视图):对第一层结果 SELECT + 窗口函数,例如
SUM(total) OVER (PARTITION BY region)算城市占大区比 - 千万别在同一个视图里既
GROUP BY ROLLUP又写SUM() OVER (),SQL Server 会报错,MySQL 5.7 会静默出错
三层固定结构(省→市→区)用递归 CTE 还是 ROLLUP
如果层级深度固定且字段明确(比如表里有 province、city、district 三列),优先用 ROLLUP(province, city, district)。
只有当结构动态(如类目树存为 parent_id)、或需向上穿透汇总(比如查某区销售额,自动包含其市、省数据)时,才上递归 CTE。
容易忽略的坑:
- ROLLUP 的层级顺序决定汇总路径:写成
ROLLUP(district, city, province)就反了,会先按区小计,再按市小计,最后全省总计——但业务上“省”才是顶层 - CTE 递归必须加深度限制,比如
WHERE level ,否则遇到脏数据(如环形引用)直接卡死 - 导出到 Excel 时,ROLLUP 的 NULL 和 CTE 的真实空值混在一起,BI 工具可能排序错乱,务必在视图末尾统一
COALESCE(region, '[全部]')

















