【MySQL全面教学】MySQL聚合函数与分组Day5(2026年)

【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 分组后筛选HAVING2.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是最常用的聚合函数但很多人对它的用法存在误解。-- 统计表中所有记录数包括NULLSELECTCOUNT(*)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.49NULL被忽略-- 计算平均订单金额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 分组后筛选HAVINGWHERE子句在分组前过滤数据HAVING在分组后过滤数据。-- 找出消费超过500元的用户SELECTuser_id,COUNT(*)ASorder_count,SUM(amount)AStotal_spentFROMordersGROUPBYuser_idHAVINGSUM(amount)500;WHERE vs HAVING对比特性WHEREHAVING执行时机分组前分组后过滤对象原始行分组后的结果可用条件任意列聚合函数或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;结果示例yearorder_counttotal_sales202315045000.00202420068000.00NULL350113000.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!cancelledGROUPBYDATE(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_idu.user_idWHEREo.order_status!cancelledGROUPBYu.cityORDERBYtotal_revenueDESCLIMIT10;4.3 用户统计-- 用户消费分层RFM模型简化版SELECTCASEWHENtotal_spent10000THEN高价值用户WHENtotal_spent5000THEN中价值用户WHENtotal_spent1000THEN普通用户ELSE低价值用户ENDASuser_segment,COUNT(*)ASuser_count,AVG(total_spent)ASavg_spentFROM(SELECTuser_id,SUM(order_amount)AStotal_spentFROMordersWHEREorder_status!cancelledGROUPBYuser_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_idu.user_idGROUPBYo.user_id;在MySQL 5.7的严格模式下上述SQL会报错。只有以下字段可以出现在SELECT中GROUP BY中的字段聚合函数的结果函数依赖的字段如主键正确写法SELECTu.user_id,u.username,COUNT(*)FROMorders oJOINusers uONo.user_idu.user_idGROUPBYu.user_id,u.username;5.2 HAVING和WHERE混用常见错误-- 低效写法SELECTuser_id,SUM(amount)FROMordersGROUPBYuser_idHAVINGorder_date2024-01-01;-- 错误HAVING不能用原始字段正确写法-- 高效写法SELECTuser_id,SUM(amount)FROMordersWHEREorder_date2024-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 → LIMIT6.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生成分组小计和总计实战应用电商系统的订单量、销售额、用户统计下一步预告Day6MySQL多表查询与JOIN明天我们将学习多表查询的核心技术——JOIN。从INNER JOIN到LEFT JOIN从自连接到UNION你将掌握如何在多个表之间进行数据关联查询。这是实际工作中最常用的技能之一敬请期待参考资料MySQL 8.0 Reference Manual - Aggregate Functions互动话题你在使用GROUP BY时遇到过哪些坑欢迎在评论区分享你们公司的数据分析场景主要用哪些聚合函数对于HAVING和WHERE的区别你有什么独特的理解方式如果觉得本文对你有帮助请点赞收藏明天见