【MySQL全面教学】MySQL聚合函数与分组Day5(2026年)
写在前面
大家好,欢迎来到MySQL全面教学系列的第5天!经过前面4天的学习,我们已经掌握了MySQL的基础操作、数据类型、表的创建与管理,以及单表查询的核心技能。今天,我们将进入数据分析的核心领域——聚合函数与分组查询。
在实际工作中,数据统计和分析是最常见的需求。无论是统计用户数量、计算销售额、还是分析订单趋势,都离不开聚合函数和GROUP BY。掌握这些技能,你就能从海量数据中提炼出有价值的信息。
让我们开始今天的学习之旅!
目录
- 写在前面
- 一、常用聚合函数
- 1.1 五大核心聚合函数
- 1.2 COUNT函数详解
- 1.3 SUM、AVG、MAX、MIN实战
- 二、GROUP BY分组
- 2.1 单字段分组
- 2.2 多字段分组
- 2.3 分组后筛选HAVING
- 2.4 完整执行顺序
- 三、WITH ROLLUP分组小计
- 四、实战:电商数据统计
- 4.1 订单量统计
- 4.2 销售额统计
- 4.3 用户统计
- 五、踩坑提醒与经验之谈
- 5.1 SELECT中出现非聚合字段
- 5.2 HAVING和WHERE混用
- 5.3 NULL值处理
- 六、面试高频考点
- 6.1 WHERE和HAVING的执行顺序?
- 6.2 GROUP BY后SELECT能写什么?
- 6.3 COUNT(*)和COUNT(1)有区别吗?
- 6.4 如何统计多列的NULL和非NULL数量?
- 6.5 GROUP BY后如何对分组结果排序?
- 七、总结
- 下一步预告
- 参考资料
- 互动话题
一、常用聚合函数
聚合函数(Aggregate Functions)用于对一组值进行计算,并返回单个值。它们是数据分析的基石。
1.1 五大核心聚合函数
| 函数 | 作用 | 返回值类型 | 忽略NULL值 |
|---|---|---|---|
| COUNT() | 统计记录数 | 整数 | 视情况而定 |
| SUM() | 求和 | 数值 | 是 |
| AVG() | 平均值 | 数值 | 是 |
| MAX() | 最大值 | 原数据类型 | 是 |
| MIN() | 最小值 | 原数据类型 | 是 |
1.2 COUNT函数详解
COUNT是最常用的聚合函数,但很多人对它的用法存在误解。
-- 统计表中所有记录数(包括NULL)SELECTCOUNT(*)FROMorders;-- 统计指定字段非NULL的记录数SELECTCOUNT(user_id)FROMorders;-- 统计去重后的记录数SELECTCOUNT(DISTINCTuser_id)FROMorders;COUNT(*) vs COUNT(字段)的区别:
| 对比项 | COUNT(*) | COUNT(字段) |
|---|---|---|
| 统计范围 | 所有行 | 字段非NULL的行 |
| 性能 | 通常更快(MySQL优化) | 需要判断NULL |
| 使用场景 | 统计总数 | 统计有值的记录 |
| 结果差异 | 包含NULL行 | 排除NULL行 |
经验之谈:如果要统计表的总行数,优先使用COUNT(*),MySQL对此有特殊优化。
1.3 SUM、AVG、MAX、MIN实战
假设我们有以下订单表:
CREATETABLEorders(order_idINTPRIMARYKEY,user_idINT,amountDECIMAL(10,2),order_dateDATE);INSERTINTOordersVALUES(1,101,199.99,'2024-01-01'),(2,102,299.50,'2024-01-02'),(3,101,150.00,'2024-01-03'),(4,103,NULL,'2024-01-04');-- 统计总销售额SELECTSUM(amount)AStotal_salesFROMorders;-- 结果:649.49(NULL被忽略)-- 计算平均订单金额SELECTAVG(amount)ASavg_amountFROMorders;-- 结果:216.50(只计算3条非NULL记录)-- 找出最大和最小订单金额SELECTMAX(amount)ASmax_amount,MIN(amount)ASmin_amountFROMorders;-- 组合使用:全面的数据统计SELECTCOUNT(*)AStotal_orders,COUNT(amount)ASvalid_orders,SUM(amount)AStotal_sales,AVG(amount)ASavg_amount,MAX(amount)ASmax_amount,MIN(amount)ASmin_amountFROMorders;踩坑提醒:AVG函数会自动忽略NULL值,但计算平均值时只基于非NULL的记录数。如果你想把NULL当作0计算,需要使用AVG(IFNULL(amount, 0))。
二、GROUP BY分组
GROUP BY用于将数据按一个或多个字段分组,然后对每组应用聚合函数。
2.1 单字段分组
-- 按用户统计订单数量和总消费SELECTuser_id,COUNT(*)ASorder_count,SUM(amount)AStotal_spentFROMordersGROUPBYuser_id;2.2 多字段分组
-- 按年份和月份统计销售额SELECTYEAR(order_date)ASyear,MONTH(order_date)ASmonth,COUNT(*)ASorder_count,SUM(amount)ASmonthly_salesFROMordersGROUPBYYEAR(order_date),MONTH(order_date)ORDERBYyear,month;2.3 分组后筛选HAVING
WHERE子句在分组前过滤数据,HAVING在分组后过滤数据。
-- 找出消费超过500元的用户SELECTuser_id,COUNT(*)ASorder_count,SUM(amount)AStotal_spentFROMordersGROUPBYuser_idHAVINGSUM(amount)>500;WHERE vs HAVING对比:
| 特性 | WHERE | HAVING |
|---|---|---|
| 执行时机 | 分组前 | 分组后 |
| 过滤对象 | 原始行 | 分组后的结果 |
| 可用条件 | 任意列 | 聚合函数或GROUP BY字段 |
| 性能 | 先过滤,数据量小 | 后过滤,数据量大 |
2.4 完整执行顺序
理解SQL的执行顺序对写出正确的查询至关重要:
1. FROM -- 确定数据来源 2. WHERE -- 过滤原始数据 3. GROUP BY -- 分组 4. HAVING -- 过滤分组结果 5. SELECT -- 选择列 6. ORDER BY -- 排序 7. LIMIT -- 限制返回行数三、WITH ROLLUP分组小计
WITH ROLLUP用于在分组结果中添加小计和总计行。
-- 按年份统计销售额,并显示总计SELECTYEAR(order_date)ASyear,COUNT(*)ASorder_count,SUM(amount)AStotal_salesFROMordersGROUPBYYEAR(order_date)WITH ROLLUP;结果示例:
| year | order_count | total_sales |
|---|---|---|
| 2023 | 150 | 45000.00 |
| 2024 | 200 | 68000.00 |
| NULL | 350 | 113000.00 |
多字段ROLLUP:
-- 按年份和月份分组,显示各级小计SELECTYEAR(order_date)ASyear,MONTH(order_date)ASmonth,SUM(amount)ASsalesFROMordersGROUPBYYEAR(order_date),MONTH(order_date)WITH ROLLUP;踩坑提醒:ROLLUP产生的总计行中,分组字段显示为NULL。如果你的数据本身就有NULL值,可能需要使用GROUPING()函数来区分。
-- 使用GROUPING函数区分NULL类型SELECTYEAR(order_date)ASyear,GROUPING(YEAR(order_date))ASis_rollup,SUM(amount)ASsalesFROMordersGROUPBYYEAR(order_date)WITH ROLLUP;四、实战:电商数据统计
假设我们有一个电商系统,包含以下表结构:
-- 用户表CREATETABLEusers(user_idINTPRIMARYKEY,usernameVARCHAR(50),register_dateDATE,cityVARCHAR(50));-- 订单表CREATETABLEorders(order_idINTPRIMARYKEY,user_idINT,order_amountDECIMAL(10,2),order_statusENUM('pending','paid','shipped','completed','cancelled'),create_timeDATETIME);-- 订单商品表CREATETABLEorder_items(item_idINTPRIMARYKEY,order_idINT,product_nameVARCHAR(100),quantityINT,unit_priceDECIMAL(10,2));4.1 订单量统计
-- 每日订单量统计SELECTDATE(create_time)ASorder_date,COUNT(*)ASorder_count,COUNT(DISTINCTuser_id)ASunique_users,SUM(order_amount)ASdaily_revenueFROMordersWHEREorder_status!='cancelled'GROUPBYDATE(create_time)ORDERBYorder_dateDESC;-- 订单状态分布SELECTorder_status,COUNT(*)AScount,ROUND(COUNT(*)*100.0/SUM(COUNT(*))OVER(),2)ASpercentageFROMordersGROUPBYorder_status;4.2 销售额统计
-- 月度销售趋势SELECTDATE_FORMAT(create_time,'%Y-%m')ASmonth,COUNT(*)ASorder_count,SUM(order_amount)ASrevenue,AVG(order_amount)ASavg_order_valueFROMordersWHEREorder_statusIN('paid','shipped','completed')GROUPBYDATE_FORMAT(create_time,'%Y-%m')ORDERBYmonth;-- 城市销售排名TOP10SELECTu.city,COUNT(DISTINCTo.user_id)ASbuyer_count,COUNT(*)ASorder_count,SUM(o.order_amount)AStotal_revenueFROMorders oJOINusers uONo.user_id=u.user_idWHEREo.order_status!='cancelled'GROUPBYu.cityORDERBYtotal_revenueDESCLIMIT10;4.3 用户统计
-- 用户消费分层(RFM模型简化版)SELECTCASEWHENtotal_spent>=10000THEN'高价值用户'WHENtotal_spent>=5000THEN'中价值用户'WHENtotal_spent>=1000THEN'普通用户'ELSE'低价值用户'ENDASuser_segment,COUNT(*)ASuser_count,AVG(total_spent)ASavg_spentFROM(SELECTuser_id,SUM(order_amount)AStotal_spentFROMordersWHEREorder_status!='cancelled'GROUPBYuser_id)tGROUPBYuser_segment;-- 新用户注册趋势SELECTDATE_FORMAT(register_date,'%Y-%m')ASmonth,COUNT(*)ASnew_usersFROMusersGROUPBYDATE_FORMAT(register_date,'%Y-%m')ORDERBYmonth;五、踩坑提醒与经验之谈
5.1 SELECT中出现非聚合字段
错误示例:
-- 错误!username不在GROUP BY中SELECTusername,COUNT(*)FROMorders oJOINusers uONo.user_id=u.user_idGROUPBYo.user_id;在MySQL 5.7+的严格模式下,上述SQL会报错。只有以下字段可以出现在SELECT中:
- GROUP BY中的字段
- 聚合函数的结果
- 函数依赖的字段(如主键)
正确写法:
SELECTu.user_id,u.username,COUNT(*)FROMorders oJOINusers uONo.user_id=u.user_idGROUPBYu.user_id,u.username;5.2 HAVING和WHERE混用
常见错误:
-- 低效写法SELECTuser_id,SUM(amount)FROMordersGROUPBYuser_idHAVINGorder_date>'2024-01-01';-- 错误!HAVING不能用原始字段正确写法:
-- 高效写法SELECTuser_id,SUM(amount)FROMordersWHEREorder_date>'2024-01-01'-- 先过滤GROUPBYuser_id;经验之谈:能用WHERE过滤的,绝不要用HAVING。WHERE在分组前过滤,减少参与分组的数据量;HAVING在分组后过滤,数据量更大。
5.3 NULL值处理
-- 统计有邮箱的用户数量SELECTCOUNT(email)FROMusers;-- 排除NULL-- 统计所有用户,没有邮箱的显示0SELECTCOUNT(IFNULL(email,''))FROMusers;-- 分组时NULL会被当作一个组SELECTcity,COUNT(*)FROMusersGROUPBYcity;-- NULL会单独显示为一行六、面试高频考点
6.1 WHERE和HAVING的执行顺序?
答案:WHERE在GROUP BY之前执行,HAVING在GROUP BY之后执行。
执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
6.2 GROUP BY后SELECT能写什么?
答案:
- GROUP BY中的字段
- 聚合函数(COUNT、SUM、AVG等)
- 与GROUP BY字段有函数依赖的字段(如主键对应的非主键字段)
在MySQL中,如果启用了ONLY_FULL_GROUP_BY模式,SELECT列表中的非聚合字段必须出现在GROUP BY子句中。
6.3 COUNT(*)和COUNT(1)有区别吗?
答案:在MySQL中没有区别,两者性能相同。COUNT(*)是标准SQL语法,推荐优先使用。
6.4 如何统计多列的NULL和非NULL数量?
SELECTCOUNT(*)AStotal,COUNT(col1)AScol1_not_null,COUNT(*)-COUNT(col1)AScol1_null,COUNT(col2)AScol2_not_null,COUNT(*)-COUNT(col2)AScol2_nullFROMtable_name;6.5 GROUP BY后如何对分组结果排序?
-- 按聚合结果排序SELECTuser_id,COUNT(*)AScntFROMordersGROUPBYuser_idORDERBYcntDESC;-- 按多个字段排序SELECTcity,COUNT(*)AScntFROMusersGROUPBYcityORDERBYcntDESC,cityASC;七、总结
今天我们学习了MySQL聚合函数与分组查询的核心知识:
- 聚合函数:COUNT、SUM、AVG、MAX、MIN的使用方法和注意事项
- GROUP BY:单字段和多字段分组,以及分组后的数据筛选
- HAVING:分组后的过滤条件,与WHERE的区别
- WITH ROLLUP:生成分组小计和总计
- 实战应用:电商系统的订单量、销售额、用户统计
下一步预告
Day6:MySQL多表查询与JOIN
明天我们将学习多表查询的核心技术——JOIN。从INNER JOIN到LEFT JOIN,从自连接到UNION,你将掌握如何在多个表之间进行数据关联查询。这是实际工作中最常用的技能之一,敬请期待!
参考资料
MySQL 8.0 Reference Manual - Aggregate Functions
互动话题
- 你在使用GROUP BY时遇到过哪些坑?欢迎在评论区分享!
- 你们公司的数据分析场景主要用哪些聚合函数?
- 对于HAVING和WHERE的区别,你有什么独特的理解方式?
如果觉得本文对你有帮助,请点赞收藏!明天见!
