用 union all 连接明细与汇总行需确保列数、顺序、类型严格一致,明细行保持原字段,汇总行对应位置补 null 或占位符,并用 row_type 标识行类型及 order by 控制排序。

用 UNION ALL 连接明细和汇总行最直接
明细行和汇总行结构不同,不能直接 GROUP BY 一起输出;必须拆成两个查询再合并。关键不是“怎么拼”,而是“怎么让字段对齐”——汇总行的非聚合字段得用 NULL 或占位符补空,否则 UNION ALL 会报列数或类型不匹配。
常见错误是汇总行漏写 NULL 占位,比如明细有 order_id、product、amount 三列,汇总行却只写了 SUM(amount),导致列数不一致。
- 明细查询保持原字段顺序,如:
SELECT order_id, product, amount FROM sales - 汇总行对应位置填
NULL(或'TOTAL'等标识),如:SELECT NULL AS order_id, 'TOTAL' AS product, SUM(amount) AS amount FROM sales - 两查字段名/别名必须一致,否则结果列头可能错乱(尤其在某些客户端里)
- 务必用
UNION ALL,不用UNION——汇总行天然唯一,去重纯属浪费性能
用 GROUPING SETS 实现一行内混合粒度(PostgreSQL / SQL Server / Oracle)
GROUPING SETS 是标准 SQL 方案,适合需要“按维度分组 + 小计 + 总计”嵌套场景,比如既要每笔订单明细,又要每个客户的金额小计,还要全部总和。它比 UNION ALL 更紧凑,但兼容性差:MySQL 不支持,SQLite 也不支持。
核心是理解 GROUPING() 函数返回值:对某列做分组时返回 0,未参与分组(即该位置是小计/总计)时返回 1。靠这个区分行类型。
- 示例(按客户+订单两级分组):
SELECT customer_id, order_id, SUM(amount), GROUPING(customer_id), GROUPING(order_id) FROM sales GROUP BY GROUPING SETS ((customer_id, order_id), (customer_id), ()) - 结果中
GROUPING(order_id) = 1表示这行是客户小计,GROUPING(customer_id) = GROUPING(order_id) = 1表示总计行 - 注意:
GROUPING SETS (())表示全表聚合,对应总计;缺括号会语法报错 - MySQL 用户绕不开,只能退回
UNION ALL方案
用 ROLLUP 实现带层级的小计(兼容性稍好)
ROLLUP 是 GROUPING SETS 的简化子集,生成“从细到粗”的递进分组,比如 GROUP BY a, b WITH ROLLUP 等价于 GROUPING SETS ((a,b), (a), ())。它比 GROUPING SETS 多几个数据库支持(如 MariaDB 10.5+),但依然不被 MySQL 原生支持(直到 8.0.12 才加,且仅限 InnoDB 表)。
- 明细行本身不在
ROLLUP结果里,得手动UNION ALL加回去 - 小计行的空字段是
NULL,不是字符串'NULL',别用IS NULL判定时混淆类型 - 排序容易混乱:默认按分组字段升序排,小计行夹在中间。通常要加
ORDER BY GROUPING(...), ...控制显示顺序 - MySQL 8.0 用户要注意:如果表用了 MyISAM 引擎,
ROLLUP会静默失效,结果不含小计行
排序与标识:让结果可读的关键细节
合并后如果不控制顺序,明细行、小计行、总计行会混在一起,完全不可读。光靠 ORDER BY 不够,得结合分组标识字段。
- 用
GROUPING()值做第一排序键(如ORDER BY GROUPING(customer_id), customer_id),确保总计行在最底 - 没
GROUPING的方案(如UNION ALL),就老实用额外字段标记类型:SELECT 'DETAIL' AS row_type, order_id, product, amount ... UNION ALL SELECT 'TOTAL', NULL, 'TOTAL', SUM(amount) ... ORDER BY row_type, order_id - 别依赖数据库默认排序:有些方言对
NULL排序规则不同(如 PostgreSQL 默认NULLS LAST,MySQL 默认NULLS FIRST),显式写NULLS LAST更稳 - 客户端渲染时,
row_type字段比靠字段值是否为NULL判断更可靠——毕竟业务字段本身也可能存NULL
真正麻烦的不是写法,而是字段对齐逻辑和排序控制。一旦列顺序或 NULL 占位出错,结果集要么报错,要么数据错位,还不好排查。











