group by本身不支持逐级汇总,需用grouping sets(postgresql/sql server)、union all(mysql 8.0+)或窗口函数实现;直接group by province,city,district仅输出最细粒度行,不会自动生成省、市小计。

GROUP BY 本身不支持“逐级”汇总,必须用多个 GROUP BY 或窗口函数模拟
SQL 的 GROUP BY 是扁平分组操作,一次只能按固定列组合聚合,无法原生实现“先按省、再按市、再按区”的嵌套层级汇总。所谓“逐级”,本质是多个不同粒度的聚合结果并置——要么用 UNION ALL 拼接多层 GROUP BY,要么用 GROUPING SETS(PostgreSQL / SQL Server / Oracle 支持),或靠窗口函数补位。
常见错误是试图写成:GROUP BY province, city, district 然后幻想能同时看到省总计、市小计、区明细——这只会输出最细粒度的分组行,上级汇总值不会自动出现。
- 若需同一结果集里同时包含省、市、区三级汇总值,
GROUPING SETS是最接近“逐级”语义的方案 - MySQL 8.0+ 不支持
GROUPING SETS,得用UNION ALL手动拼三层GROUP BY,注意用NULL占位对齐列 - 想在每行保留上级汇总(比如每条区记录旁显示所在市的总销售额),该用
SUM() OVER (PARTITION BY city)这类窗口函数,不是GROUP BY
用 GROUPING SETS 实现三层次汇总(PostgreSQL / SQL Server)
GROUPING SETS 允许在一个查询中指定多组分组维度,数据库会分别计算并合并结果。例如要同时得到省、市、区三级销售总额:
SELECT COALESCE(province, 'ALL') AS province, COALESCE(city, 'ALL') AS city, COALESCE(district, 'ALL') AS district, SUM(sales) AS total_sales FROM sales_data GROUP BY GROUPING SETS ( (province), (province, city), (province, city, district) );
COALESCE 用来把 GROUPING SETS 生成的 NULL 占位替换成可读标识(如 'ALL'),否则你看到的是空字符串或 NULL,难以区分层级。
- 顺序不重要,但每组括号内列必须是前缀关系(如不能单独写
(city)而不带province,除非业务允许跨省聚合) - 结果行数 = 各级分组行数之和,可能远大于原始表行数,注意性能
- PostgreSQL 中需确保列名在所有 grouping set 中都存在,否则报错
column "xxx" must appear in the GROUP BY clause
MySQL 用户:用 UNION ALL 模拟 GROUPING SETS
MySQL 8.0 不支持 GROUPING SETS,但可以用 UNION ALL 把三次独立 GROUP BY 结果合并,并用 NULL 或字符串占位对齐字段:
SELECT province, NULL AS city, NULL AS district, SUM(sales) AS total_sales FROM sales_data GROUP BY province UNION ALL SELECT province, city, NULL AS district, SUM(sales) AS total_sales FROM sales_data GROUP BY province, city UNION ALL SELECT province, city, district, SUM(sales) AS total_sales FROM sales_data GROUP BY province, city, district ORDER BY province, city NULLS FIRST, district NULLS FIRST;
关键点是每层 SELECT 的列数、类型、顺序必须一致,缺失维度用 NULL 补齐;ORDER BY 中的 NULLS FIRST 让汇总行排在明细前(MySQL 8.0+ 支持,5.7 需改用 IF(ISNULL(city), 0, 1) 排序)。
- 避免漏加
ORDER BY—— 否则各级结果混在一起,无法识别层级 - 如果某层数据量极大(如千万级区划明细),三次全表扫描代价高,考虑物化中间结果或加覆盖索引
- 别用
UNION(去重),会导致同值汇总被合并,必须用UNION ALL
为什么不该在 GROUP BY 里混用明细字段和聚合字段?
典型错误写法:SELECT province, city, district, SUM(sales), MAX(order_date) FROM sales_data GROUP BY province。这在 MySQL 5.7 严格模式下直接报错 Expression #3 of SELECT list is not in GROUP BY clause,在其他数据库也可能返回不可靠值。
根本原因是:当只按 province 分组时,city 和 district 对每个省有多个取值,数据库无法确定该返回哪一个。即使查询通过,city 值也是随机选取的(非标准行为)。
- 正确做法是:每一层汇总只 SELECT 当前层级及更粗粒度的字段,明细字段(如
district)只出现在最细粒度的 grouping set 或子查询中 - 想关联明细(如查出每个省销售额最高的那个市),得用窗口函数(
ROW_NUMBER() OVER (PARTITION BY province ORDER BY SUM(sales) DESC))或关联子查询 - 任何试图让
GROUP BY“记住”下级明细的尝试,都会掉进非确定性陷阱










