【自记】SQL 中 GROUPING 和 GROUPING SETS 语句的案例说明
我们用一个生活中的例子来理解,比如你开了家小超市,想统计「销售额」,但需要从多个角度看(比如按 “日期 + 商品”、“仅日期”、“仅商品”、“整体总销售额”)。
假设你的销售数据长这样(简化版):
日期 | 商品 | 销售额 |
---|---|---|
2023-10-01 | 可乐 | 100 |
2023-10-01 | 薯片 | 80 |
2023-10-02 | 可乐 | 120 |
2023-10-02 | 薯片 | 90 |
你想同时得到 4 种统计结果:
- 按「日期 + 商品」统计(最明细的维度);
- 仅按「日期」统计(每天总销售额);
- 仅按「商品」统计(每种商品总销售额);
- 不按任何维度(整体总销售额)。
不用 GROUPING SETS 的话,你需要写 4 个查询,再合并:
-- 1. 日期+商品
SELECT 日期, 商品, SUM(销售额) FROM 销售表 GROUP BY 日期, 商品
UNION ALL
-- 2. 仅日期
SELECT 日期, 'ALL' 商品, SUM(销售额) FROM 销售表 GROUP BY 日期
UNION ALL
-- 3. 仅商品
SELECT 'ALL' 日期, 商品, SUM(销售额) FROM 销售表 GROUP BY 商品
UNION ALL
-- 4. 整体汇总
SELECT 'ALL' 日期, 'ALL' 商品, SUM(销售额) FROM 销售表
用 GROUPING SETS 的话,1 条查询搞定:
SELECT -- 用GROUPING判断列是否参与分组,不参与就显示'ALL'IF(GROUPING(日期) = 0, 日期, 'ALL') AS 日期,IF(GROUPING(商品) = 0, 商品, 'ALL') AS 商品,SUM(销售额) AS 总销售额
FROM 销售表
-- 一次性指定所有要统计的维度组合
GROUP BY 日期, 商品
GROUPING SETS ((日期, 商品), -- 对应需求1:日期+商品(日期), -- 对应需求2:仅日期(商品), -- 对应需求3:仅商品() -- 对应需求4:不分组(整体汇总)
)
最终结果长这样:
日期 | 商品 | 总销售额 | |
---|---|---|---|
2023-10-01 | 可乐 | 100 | -- 日期 + 商品维度 |
2023-10-01 | 薯片 | 80 | -- 日期 + 商品维度 |
2023-10-02 | 可乐 | 120 | -- 日期 + 商品维度 |
2023-10-02 | 薯片 | 90 | -- 日期 + 商品维度 |
2023-10-01 | ALL | 180 | -- 仅日期维度(100+80) |
2023-10-02 | ALL | 210 | -- 仅日期维度(120+90) |
ALL | 可乐 | 220 | -- 仅商品维度(100+120) |
ALL | 薯片 | 170 | -- 仅商品维度(80+90) |
ALL | ALL | 390 | -- 整体汇总(180+210 或 220+170) |
核心点总结:
GROUPING SETS(...)
:括号里写多个 “维度组合”,一次查询得到所有组合的统计结果(代替多次GROUP BY
+UNION ALL
)。GROUPING(列名)
:判断这一列是否在当前行的 “维度组合” 中。如果在(参与了分组),返回 0,显示实际值;如果不在(是汇总行),返回 1,用 'ALL' 标记,方便区分。
————————————
我们用一个更贴近实际业务的例子来说明:假设你有一张「订单表」,需要统计不同维度的销量,同时保留一个固定的分组字段(比如「月份」),并穿插普通查询字段和GROUPING
处理的字段,看看它们的区别。
场景设定
订单表orders
结构(简化):
月份(month) | 地区(region) | 产品(product) | 销量(sales) |
---|---|---|---|
2023-09 | 华北 | 手机 | 100 |
2023-09 | 华北 | 电脑 | 50 |
2023-09 | 华南 | 手机 | 80 |
2023-10 | 华北 | 手机 | 120 |
2023-10 | 华南 | 电脑 | 60 |
需求
统计每个月的销量,同时按以下维度组合分析:
- 月份 + 地区 + 产品(最明细)
- 月份 + 地区(不区分产品)
- 月份 + 产品(不区分地区)
- 月份(不区分地区和产品)
要求结果中:
- 保留「月份」作为固定显示的普通字段;
- 「地区」和「产品」用
GROUPING
处理,不参与分组时显示'ALL'
; - 计算总销量。
SQL 查询(包含普通字段和 GROUPING 字段)
SELECT -- 普通字段:月份(始终在GROUP BY中,直接显示实际值)month,-- GROUPING处理的字段:地区(是否参与分组动态显示)IF(GROUPING(region) = 0, region, 'ALL') AS region,-- GROUPING处理的字段:产品(是否参与分组动态显示)IF(GROUPING(product) = 0, product, 'ALL') AS product,-- 聚合字段:总销量SUM(sales) AS total_sales
FROM orders
-- 固定按月份分组,同时用GROUPING SETS定义地区和产品的组合
GROUP BY month,
GROUPING SETS ((region, product), -- 组合1:月份+地区+产品(region), -- 组合2:月份+地区(无产品)(product), -- 组合3:月份+产品(无地区)() -- 组合4:仅月份(无地区和产品)
)
ORDER BY month, region, product;
查询结果
month | region | product | total_sales | |
---|---|---|---|---|
2023-09 | 华北 | 手机 | 100 | -- 组合 1:月份 + 地区 + 产品 |
2023-09 | 华北 | 电脑 | 50 | -- 组合 1:月份 + 地区 + 产品 |
2023-09 | 华北 | ALL | 150 | -- 组合 2:月份 + 地区(汇总该地区所有产品) |
2023-09 | 华南 | 手机 | 80 | -- 组合 1:月份 + 地区 + 产品 |
2023-09 | 华南 | ALL | 80 | -- 组合 2:月份 + 地区(汇总该地区所有产品) |
2023-09 | ALL | 手机 | 180 | -- 组合 3:月份 + 产品(汇总所有地区该产品) |
2023-09 | ALL | 电脑 | 50 | -- 组合 3:月份 + 产品(汇总所有地区该产品) |
2023-09 | ALL | ALL | 230 | -- 组合 4:仅月份(汇总该月所有销量) |
2023-10 | 华北 | 手机 | 120 | -- 组合 1:月份 + 地区 + 产品 |
2023-10 | 华北 | ALL | 120 | -- 组合 2:月份 + 地区(汇总该地区所有产品) |
2023-10 | 华南 | 电脑 | 60 | -- 组合 1:月份 + 地区 + 产品 |
2023-10 | 华南 | ALL | 60 | -- 组合 2:月份 + 地区(汇总该地区所有产品) |
2023-10 | ALL | 手机 | 120 | -- 组合 3:月份 + 产品(汇总所有地区该产品) |
2023-10 | ALL | 电脑 | 60 | -- 组合 3:月份 + 产品(汇总所有地区该产品) |
2023-10 | ALL | ALL | 180 | -- 组合 4:仅月份(汇总该月所有销量) |
普通字段 vs GROUPING 处理的字段:核心区别
普通字段(如
month
):- 必须出现在
GROUP BY
中(否则 SQL 会报错,因为非聚合字段必须参与分组)。 - 其值是「固定分组维度」,在所有结果行中都显示实际值(如
2023-09
、2023-10
),不会被替换为'ALL'
。 - 作用:作为所有统计维度的 “基础锚点”(比如这里所有统计都基于 “月份” 展开)。
- 必须出现在
GROUPING 处理的字段(如
region
、product
):- 不一定在所有分组组合中出现(由
GROUPING SETS
控制)。 - 其值是「动态分组维度」:当参与当前分组时显示实际值(如
华北
、手机
),不参与时显示'ALL'
(表示该维度被汇总)。 - 作用:灵活切换不同维度的统计视角,同时用
'ALL'
清晰标记 “当前行是该维度的汇总结果”。
- 不一定在所有分组组合中出现(由
简单说:普通字段是 “固定不变的分组锚点”,GROUPING 处理的字段是 “可开关的动态维度”,前者值固定,后者值随分组组合动态变化(实际值或汇总标记)。