正确写法是:group by dept_name, account_code with rollup,配合grouping()函数识别层级,按字段从左到右顺序生成明细、部门小计、公司总计四层结果,并用case语句语义化标注汇总行。

用 GROUP BY + ROLLUP 实现多级汇总的正确写法
直接用 GROUP BY 做财务报表分组,通常只能得到最细粒度结果;要同时展示「部门+科目」、「部门小计」、「公司总计」三层,ROLLUP 是最稳妥的选择。它按字段顺序生成层级聚合,顺序错了汇总逻辑就乱。
-
ROLLUP(a, b, c)会生成 (a,b,c)、(a,b,null)、(a,null,null)、(null,null,null) 四层,对应明细 → 部门内科目合计 → 部门合计 → 全公司合计 - MySQL 8.0+ 和 PostgreSQL 12+ 支持标准
ROLLUP;旧版 MySQL 只能靠UNION ALL拼接,但易漏空行、难对齐 - 注意:SQL Server 的
ROLLUP语法相同,但GROUPING()函数返回值是 int(0/1),而 PostgreSQL 返回 boolean,判断空汇总行时别写错
区分汇总行与明细行的关键函数:GROUPING() 和 GROUPING_ID()
报表里必须标出「小计」「总计」这类汇总行,否则用户根本分不清哪行是算出来的。不能靠字段是否为 NULL 判断——因为原始数据本身可能存 NULL。
-
GROUPING(dept_name)返回 1 表示该行是 dept_name 的汇总行(即 dept_name 为 NULL 是系统填充的),返回 0 表示真实数据 - 多个字段时,
GROUPING_ID(dept_name, account_code)返回一个整数,二进制位对应各字段的GROUPING()结果,比如GROUPING_ID(a,b)=2即GROUPING(a)=1且GROUPING(b)=0,表示 a 汇总、b 保留 - Oracle 和 SQL Server 支持
GROUPING_ID();PostgreSQL 只支持单字段GROUPING(),多字段需手动组合
财务科目树形结构下的递归汇总(如:收入→主营业务收入→软件销售)
如果科目表带 parent_id,想按科目层级自动汇总(上级科目 = 所有下级科目之和),就得用递归 CTE,不能只靠 ROLLUP。
- 先用递归 CTE 把每个末级科目的完整路径查出来,例如
path = '1.101.10101',再用SUBSTRING_INDEX(MySQL)或SPLIT_PART(PostgreSQL)截取各级前缀做分组 - 避免在递归里直接求和——会导致重复计算;正确做法是:递归只展开层级关系,求和放在外层
GROUP BY中进行 - SQL Server 的
HIERARCHYID类型可加速树查询,但迁移成本高;多数场景用标准递归 CTE 更通用
性能陷阱:ROLLUP 在大表上容易慢,怎么优化?
ROLLUP 本质是多趟扫描+聚合,1000 万行以上账务表直接跑可能卡住。不是加索引就能解决——因为 ROLLUP 不走索引范围扫描。
- 优先物化中间结果:把明细数据按关键维度(如
period_id,dept_id,account_code)预聚合到汇总表,再对汇总表做ROLLUP - 禁用
SELECT *:只选真正需要的字段,尤其避免带大文本字段(如摘要说明),它们会拖慢内存排序 - PostgreSQL 可用
MATERIALIZED VIEW自动刷新;MySQL 只能靠定时任务重建汇总表,记得加WHERE period_id >= ?控制范围
真正麻烦的是跨年滚动汇总和币种折算混在一起的情况——这时候分组逻辑得拆成两层:先按原币种聚合,再统一折算,顺序反了数字就对不上。











