SQL GROUP BY 语句(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
在数据查询的世界里,SQL GROUP BY
语句如同一把钥匙,能够解锁海量数据中的隐藏模式。无论是统计销售数据、分析用户行为,还是优化数据库性能,开发者都需要掌握这一核心工具。本文将通过循序渐进的讲解,结合实际案例和代码示例,帮助读者全面理解 SQL GROUP BY
的原理与应用场景。
一、基础概念:什么是 GROUP BY?
1.1 分组的直观理解
GROUP BY
是 SQL 中用于将查询结果按指定列分组的语句。想象一个图书馆的书架:若想统计每个分类(如“小说”“科技”“历史”)的书籍数量,可以通过将书籍按分类标签分组,再对每组进行计数。GROUP BY
的作用正是如此——它将表中的行划分到不同的逻辑组中,随后可对每个组执行聚合计算(如求和、平均值、计数等)。
1.2 基本语法结构
SELECT 列1, 列2, 聚合函数(列3)
FROM 表名
WHERE 条件
GROUP BY 列1, 列2
ORDER BY 列;
关键点:
GROUP BY
必须与聚合函数(如SUM()
,COUNT()
,AVG()
)配合使用,否则无法直接返回非分组列的原始数据。- 分组字段需出现在
SELECT
子句中,除非使用聚合函数。
二、核心用法:从简单到复杂
2.1 基本分组:单列分组
案例场景:某电商数据库有 sales
表,包含 product_id
, category
, price
, sale_date
等字段。需求是统计每个商品类别的总销售额。
SELECT category, SUM(price) AS total_sales
FROM sales
GROUP BY category;
输出结果示例:
| category | total_sales |
|------------|-------------|
| 电子产品 | 150000 |
| 家居用品 | 80000 |
| 图书 | 30000 |
2.2 多列分组:细分数据维度
若需按多个字段分组(例如按“类别”和“销售月份”统计),只需在 GROUP BY
中添加字段:
SELECT category,
DATE_TRUNC('month', sale_date) AS sale_month,
SUM(price) AS total_sales
FROM sales
GROUP BY category, DATE_TRUNC('month', sale_date)
ORDER BY sale_month DESC;
此查询将生成按月分组的销售数据,帮助分析不同类别的季节性趋势。
三、聚合函数:与 GROUP BY 的协同作用
3.1 常见聚合函数详解
- COUNT():统计组内行数。
- SUM():计算数值列的总和。
- AVG():计算数值列的平均值。
- MAX() / MIN():获取列中的最大值或最小值。
案例:统计每个用户的订单数量及平均订单金额:
SELECT user_id,
COUNT(order_id) AS order_count,
AVG(amount) AS avg_order_amount
FROM orders
GROUP BY user_id;
3.2 聚合函数的隐式分组
即使未显式使用 GROUP BY
,某些聚合函数(如 SUM(*)
)会默认对全表进行分组。但若需按条件分组,必须显式声明。
四、进阶技巧:HAVING 子句与子句顺序
4.1 HAVING:筛选分组后的结果
HAVING
是 GROUP BY
的“过滤器”,用于筛选满足条件的组。例如,仅显示总销售额超过 10 万元的类别:
SELECT category, SUM(price) AS total_sales
FROM sales
GROUP BY category
HAVING SUM(price) > 100000;
注意:HAVING
需作用于聚合结果,而 WHERE
用于过滤原始数据行。
4.2 子句的正确顺序
SQL 语句的执行顺序为:
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
这意味着 GROUP BY
必须在 HAVING
之前,而聚合函数需在 SELECT
中定义。
五、高级场景:窗口函数与多级分组
5.1 窗口函数与 GROUP BY 的对比
GROUP BY
是“行聚合”,而窗口函数(如 OVER()
)可在保留原始行的基础上进行分组计算。例如,计算每个用户订单金额的排名:
SELECT user_id,
amount,
RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS order_rank
FROM orders;
此查询保留所有订单行,但按用户分组并排序。
5.2 多级分组与 ROLLUP/CUBE
ROLLUP
和 CUBE
可自动生成嵌套分组。例如:
SELECT category,
DATE_TRUNC('month', sale_date) AS sale_month,
SUM(price) AS total_sales
FROM sales
GROUP BY ROLLUP(category, sale_month);
此查询会返回“按类别和月份分组”“仅按类别分组”“总销售额”三组结果,适合生成多层级报表。
六、常见陷阱与解决方案
6.1 非分组列的错误引用
错误示例:
SELECT category, price, SUM(price)
FROM sales
GROUP BY category; -- 错误!price 未聚合或分组
解决方法:
- 将
price
放入聚合函数(如AVG(price)
)或添加到GROUP BY
子句中。
6.2 分组字段顺序的影响
多字段分组时,顺序决定分组优先级。例如:
GROUP BY category, sale_month; -- 先按 category 分组,再按 sale_month
若顺序调换,分组逻辑将完全不同。
七、实战案例:电商销售分析
7.1 案例背景
假设某电商平台有以下表结构:
users
(用户表):user_id
,registration_date
,country
orders
(订单表):order_id
,user_id
,amount
,order_date
7.2 案例问题与解决方案
问题 1:统计各国用户的订单数量及平均订单金额。
SELECT u.country,
COUNT(DISTINCT o.order_id) AS total_orders,
AVG(o.amount) AS avg_order_amount
FROM users u
JOIN orders o ON u.user_id = o.user_id
GROUP BY u.country;
问题 2:找出订单金额前 10% 的用户。
SELECT user_id,
SUM(amount) AS total_spent
FROM orders
GROUP BY user_id
HAVING SUM(amount) >= (
SELECT PERCENTILE_CONT(0.9)
WITHIN GROUP (ORDER BY total)
FROM (
SELECT SUM(amount) AS total
FROM orders
GROUP BY user_id
) AS subquery
);
结论
SQL GROUP BY
语句是数据分析师和开发者的必备工具,它通过分组与聚合,将原始数据转化为可洞察的信息。无论是基础的统计需求,还是复杂的多级分组,掌握其原理与技巧都能显著提升数据处理效率。建议读者通过实际项目练习,结合 HAVING
、窗口函数等进阶功能,逐步解锁 GROUP BY
的全部潜力。
通过本文的学习,读者应能理解:
- 如何用
GROUP BY
实现数据分组与聚合计算; - 区分
WHERE
与HAVING
的使用场景; - 通过案例掌握多列分组、嵌套查询等复杂操作。
掌握这些技能后,开发者将能更高效地从数据中提取价值,为业务决策提供坚实的技术支持。