SQL的GROUP BY子句默认支持对单个字段进行分组,当需要按照多个维度同时统计数据时,就需要在GROUP BY子句中同时指定多个列名,实现多个字段的联合分组。联合分组会先按照第一个字段分组,再在每个分组内按照第二个字段继续分组,以此类推,最终得到多维度交叉的统计结果。

联合分组的基本语法
SQL中多字段联合分组的语法格式非常简单,只需要在GROUP BY关键字后依次列出需要分组的列名,列名之间用英文逗号分隔即可,基本语法如下:
SELECT 分组列1, 分组列2, 聚合函数(统计列) FROM 表名 WHERE 筛选条件 GROUP BY 分组列1, 分组列2 ORDER BY 排序条件;
需要注意,SELECT子句中出现的非聚合函数的列,必须全部出现在GROUP BY子句的分组列列表中,否则查询会报错,这是SQL分组查询的基本规则。
实际使用示例
假设我们有一张销售记录表sales_record,表结构如下:
| 列名 | 类型 | 说明 |
|---|---|---|
| id | INT | 记录ID |
| region | VARCHAR | 销售区域 |
| product_type | VARCHAR | 产品类型 |
| sale_amount | DECIMAL | 销售金额 |
| sale_date | DATE | 销售日期 |
场景1:统计不同区域不同产品类型的总销售额
我们需要按照区域和产品类型两个维度联合分组,计算每个分组的销售总金额,查询语句如下:
SELECT region, product_type, SUM(sale_amount) AS total_sale FROM sales_record GROUP BY region, product_type ORDER BY region, total_sale DESC;
这条语句的执行逻辑是:首先按照region字段将所有记录分成不同的区域组,然后在每个区域组内,再按照product_type字段分成不同的产品类型组,最后对每个最小粒度的分组计算sale_amount的总和。
场景2:带筛选条件的联合分组
如果需要统计2024年之后的不同区域不同产品类型的平均销售额,只需要在GROUP BY之前添加WHERE筛选条件即可:
SELECT region, product_type, AVG(sale_amount) AS avg_sale FROM sales_record WHERE sale_date >= '2024-01-01' GROUP BY region, product_type HAVING avg_sale > 1000;
这里额外使用了HAVING子句对分组后的结果进行筛选,HAVING和WHERE的区别是,WHERE是对原始数据进行筛选,HAVING是对分组后的聚合结果进行筛选。
联合分组的注意事项
- 分组列的顺序会影响分组的中间过程,但不会影响最终的分组结果集合,不过会影响默认排序的结果,建议按照业务统计的维度优先级排列分组列。
- 如果分组列中存在NULL值,所有NULL值会被归为同一个分组,这是SQL处理NULL值的默认规则。
- 联合分组可以搭配所有常见的聚合函数使用,比如COUNT、SUM、AVG、MAX、MIN等,聚合函数会对每个最小粒度分组内的数据进行计算。
- 部分数据库支持在GROUP BY子句中使用列的位置序号代替列名,比如GROUP BY 1,2代表按照SELECT后的第一列和第二列分组,但这种写法可读性较差,不建议在生产环境使用。
与其他分组方式的区别
单个字段分组只会按照一个维度统计数据,而联合分组可以得到多维度交叉的统计结果,比如单个字段按照区域分组只能得到每个区域的总销售额,而联合区域和产品类型分组可以得到每个区域下每个产品类型的销售额,统计粒度更细,能满足更复杂的业务分析需求。
如果需要对分组后的结果做更灵活的处理,比如按照多个字段排序,可以在ORDER BY子句中指定多个排序字段,排序字段的顺序会决定最终结果的排列顺序。