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

写在前面

大家好,欢迎来到MySQL全面教学系列的第5天!经过前面4天的学习,我们已经掌握了MySQL的基础操作、数据类型、表的创建与管理,以及单表查询的核心技能。今天,我们将进入数据分析的核心领域------聚合函数与分组查询。

在实际工作中,数据统计和分析是最常见的需求。无论是统计用户数量、计算销售额、还是分析订单趋势,都离不开聚合函数和GROUP BY。掌握这些技能,你就能从海量数据中提炼出有价值的信息。

让我们开始今天的学习之旅!


目录

    • 写在前面
    • 一、常用聚合函数
      • [1.1 五大核心聚合函数](#1.1 五大核心聚合函数)
      • [1.2 COUNT函数详解](#1.2 COUNT函数详解)
      • [1.3 SUM、AVG、MAX、MIN实战](#1.3 SUM、AVG、MAX、MIN实战)
    • [二、GROUP BY分组](#二、GROUP BY分组)
      • [2.1 单字段分组](#2.1 单字段分组)
      • [2.2 多字段分组](#2.2 多字段分组)
      • [2.3 分组后筛选HAVING](#2.3 分组后筛选HAVING)
      • [2.4 完整执行顺序](#2.4 完整执行顺序)
    • [三、WITH ROLLUP分组小计](#三、WITH ROLLUP分组小计)
    • 四、实战:电商数据统计
      • [4.1 订单量统计](#4.1 订单量统计)
      • [4.2 销售额统计](#4.2 销售额统计)
      • [4.3 用户统计](#4.3 用户统计)
    • 五、踩坑提醒与经验之谈
      • [5.1 SELECT中出现非聚合字段](#5.1 SELECT中出现非聚合字段)
      • [5.2 HAVING和WHERE混用](#5.2 HAVING和WHERE混用)
      • [5.3 NULL值处理](#5.3 NULL值处理)
    • 六、面试高频考点
      • [6.1 WHERE和HAVING的执行顺序?](#6.1 WHERE和HAVING的执行顺序?)
      • [6.2 GROUP BY后SELECT能写什么?](#6.2 GROUP BY后SELECT能写什么?)
      • [6.3 COUNT(*)和COUNT(1)有区别吗?](#6.3 COUNT(*)和COUNT(1)有区别吗?)
      • [6.4 如何统计多列的NULL和非NULL数量?](#6.4 如何统计多列的NULL和非NULL数量?)
      • [6.5 GROUP BY后如何对分组结果排序?](#6.5 GROUP BY后如何对分组结果排序?)
    • 七、总结
    • 参考资料
    • 互动话题

一、常用聚合函数

聚合函数(Aggregate Functions)用于对一组值进行计算,并返回单个值。它们是数据分析的基石。

1.1 五大核心聚合函数

函数 作用 返回值类型 忽略NULL值
COUNT() 统计记录数 整数 视情况而定
SUM() 求和 数值 是
AVG() 平均值 数值 是
MAX() 最大值 原数据类型 是
MIN() 最小值 原数据类型 是

1.2 COUNT函数详解

COUNT是最常用的聚合函数,但很多人对它的用法存在误解。

sql 复制代码
-- 统计表中所有记录数(包括NULL)
SELECT COUNT(*) FROM orders;

-- 统计指定字段非NULL的记录数
SELECT COUNT(user_id) FROM orders;

-- 统计去重后的记录数
SELECT COUNT(DISTINCT user_id) FROM orders;

COUNT(*) vs COUNT(字段)的区别:

对比项 COUNT(*) COUNT(字段)
统计范围 所有行 字段非NULL的行
性能 通常更快(MySQL优化) 需要判断NULL
使用场景 统计总数 统计有值的记录
结果差异 包含NULL行 排除NULL行

经验之谈: 如果要统计表的总行数,优先使用COUNT(*),MySQL对此有特殊优化。

1.3 SUM、AVG、MAX、MIN实战

假设我们有以下订单表:

sql 复制代码
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2),
    order_date DATE
);

INSERT INTO orders VALUES
(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');
sql 复制代码
-- 统计总销售额
SELECT SUM(amount) AS total_sales FROM orders;
-- 结果:649.49(NULL被忽略)

-- 计算平均订单金额
SELECT AVG(amount) AS avg_amount FROM orders;
-- 结果:216.50(只计算3条非NULL记录)

-- 找出最大和最小订单金额
SELECT 
    MAX(amount) AS max_amount,
    MIN(amount) AS min_amount
FROM orders;

-- 组合使用:全面的数据统计
SELECT 
    COUNT(*) AS total_orders,
    COUNT(amount) AS valid_orders,
    SUM(amount) AS total_sales,
    AVG(amount) AS avg_amount,
    MAX(amount) AS max_amount,
    MIN(amount) AS min_amount
FROM orders;

踩坑提醒: AVG函数会自动忽略NULL值,但计算平均值时只基于非NULL的记录数。如果你想把NULL当作0计算,需要使用AVG(IFNULL(amount, 0))。


二、GROUP BY分组

GROUP BY用于将数据按一个或多个字段分组,然后对每组应用聚合函数。

2.1 单字段分组

sql 复制代码
-- 按用户统计订单数量和总消费
SELECT 
    user_id,
    COUNT(*) AS order_count,
    SUM(amount) AS total_spent
FROM orders
GROUP BY user_id;

2.2 多字段分组

sql 复制代码
-- 按年份和月份统计销售额
SELECT 
    YEAR(order_date) AS year,
    MONTH(order_date) AS month,
    COUNT(*) AS order_count,
    SUM(amount) AS monthly_sales
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date)
ORDER BY year, month;

2.3 分组后筛选HAVING

WHERE子句在分组前过滤数据,HAVING在分组后过滤数据。

sql 复制代码
-- 找出消费超过500元的用户
SELECT 
    user_id,
    COUNT(*) AS order_count,
    SUM(amount) AS total_spent
FROM orders
GROUP BY user_id
HAVING SUM(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用于在分组结果中添加小计和总计行。

sql 复制代码
-- 按年份统计销售额,并显示总计
SELECT 
    YEAR(order_date) AS year,
    COUNT(*) AS order_count,
    SUM(amount) AS total_sales
FROM orders
GROUP BY YEAR(order_date) WITH ROLLUP;

结果示例:

year order_count total_sales
2023 150 45000.00
2024 200 68000.00
NULL 350 113000.00

多字段ROLLUP:

sql 复制代码
-- 按年份和月份分组,显示各级小计
SELECT 
    YEAR(order_date) AS year,
    MONTH(order_date) AS month,
    SUM(amount) AS sales
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date) WITH ROLLUP;

踩坑提醒: ROLLUP产生的总计行中,分组字段显示为NULL。如果你的数据本身就有NULL值,可能需要使用GROUPING()函数来区分。

sql 复制代码
-- 使用GROUPING函数区分NULL类型
SELECT 
    YEAR(order_date) AS year,
    GROUPING(YEAR(order_date)) AS is_rollup,
    SUM(amount) AS sales
FROM orders
GROUP BY YEAR(order_date) WITH ROLLUP;

四、实战:电商数据统计

假设我们有一个电商系统,包含以下表结构:

sql 复制代码
-- 用户表
CREATE TABLE users (
    user_id INT PRIMARY KEY,
    username VARCHAR(50),
    register_date DATE,
    city VARCHAR(50)
);

-- 订单表
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    order_amount DECIMAL(10,2),
    order_status ENUM('pending', 'paid', 'shipped', 'completed', 'cancelled'),
    create_time DATETIME
);

-- 订单商品表
CREATE TABLE order_items (
    item_id INT PRIMARY KEY,
    order_id INT,
    product_name VARCHAR(100),
    quantity INT,
    unit_price DECIMAL(10,2)
);

4.1 订单量统计

sql 复制代码
-- 每日订单量统计
SELECT 
    DATE(create_time) AS order_date,
    COUNT(*) AS order_count,
    COUNT(DISTINCT user_id) AS unique_users,
    SUM(order_amount) AS daily_revenue
FROM orders
WHERE order_status != 'cancelled'
GROUP BY DATE(create_time)
ORDER BY order_date DESC;

-- 订单状态分布
SELECT 
    order_status,
    COUNT(*) AS count,
    ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) AS percentage
FROM orders
GROUP BY order_status;

4.2 销售额统计

sql 复制代码
-- 月度销售趋势
SELECT 
    DATE_FORMAT(create_time, '%Y-%m') AS month,
    COUNT(*) AS order_count,
    SUM(order_amount) AS revenue,
    AVG(order_amount) AS avg_order_value
FROM orders
WHERE order_status IN ('paid', 'shipped', 'completed')
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
ORDER BY month;

-- 城市销售排名TOP10
SELECT 
    u.city,
    COUNT(DISTINCT o.user_id) AS buyer_count,
    COUNT(*) AS order_count,
    SUM(o.order_amount) AS total_revenue
FROM orders o
JOIN users u ON o.user_id = u.user_id
WHERE o.order_status != 'cancelled'
GROUP BY u.city
ORDER BY total_revenue DESC
LIMIT 10;

4.3 用户统计

sql 复制代码
-- 用户消费分层(RFM模型简化版)
SELECT 
    CASE 
        WHEN total_spent >= 10000 THEN '高价值用户'
        WHEN total_spent >= 5000 THEN '中价值用户'
        WHEN total_spent >= 1000 THEN '普通用户'
        ELSE '低价值用户'
    END AS user_segment,
    COUNT(*) AS user_count,
    AVG(total_spent) AS avg_spent
FROM (
    SELECT 
        user_id,
        SUM(order_amount) AS total_spent
    FROM orders
    WHERE order_status != 'cancelled'
    GROUP BY user_id
) t
GROUP BY user_segment;

-- 新用户注册趋势
SELECT 
    DATE_FORMAT(register_date, '%Y-%m') AS month,
    COUNT(*) AS new_users
FROM users
GROUP BY DATE_FORMAT(register_date, '%Y-%m')
ORDER BY month;

五、踩坑提醒与经验之谈

5.1 SELECT中出现非聚合字段

错误示例:

sql 复制代码
-- 错误!username不在GROUP BY中
SELECT username, COUNT(*) 
FROM orders o 
JOIN users u ON o.user_id = u.user_id
GROUP BY o.user_id;

在MySQL 5.7+的严格模式下,上述SQL会报错。只有以下字段可以出现在SELECT中:

  • GROUP BY中的字段
  • 聚合函数的结果
  • 函数依赖的字段(如主键)

正确写法:

sql 复制代码
SELECT u.user_id, u.username, COUNT(*) 
FROM orders o 
JOIN users u ON o.user_id = u.user_id
GROUP BY u.user_id, u.username;

5.2 HAVING和WHERE混用

常见错误:

sql 复制代码
-- 低效写法
SELECT user_id, SUM(amount)
FROM orders
GROUP BY user_id
HAVING order_date > '2024-01-01';  -- 错误!HAVING不能用原始字段

正确写法:

sql 复制代码
-- 高效写法
SELECT user_id, SUM(amount)
FROM orders
WHERE order_date > '2024-01-01'  -- 先过滤
GROUP BY user_id;

经验之谈: 能用WHERE过滤的,绝不要用HAVING。WHERE在分组前过滤,减少参与分组的数据量;HAVING在分组后过滤,数据量更大。

5.3 NULL值处理

sql 复制代码
-- 统计有邮箱的用户数量
SELECT COUNT(email) FROM users;  -- 排除NULL

-- 统计所有用户,没有邮箱的显示0
SELECT COUNT(IFNULL(email, '')) FROM users;

-- 分组时NULL会被当作一个组
SELECT city, COUNT(*) FROM users GROUP BY city;
-- 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数量?

sql 复制代码
SELECT 
    COUNT(*) AS total,
    COUNT(col1) AS col1_not_null,
    COUNT(*) - COUNT(col1) AS col1_null,
    COUNT(col2) AS col2_not_null,
    COUNT(*) - COUNT(col2) AS col2_null
FROM table_name;

6.5 GROUP BY后如何对分组结果排序?

sql 复制代码
-- 按聚合结果排序
SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id
ORDER BY cnt DESC;

-- 按多个字段排序
SELECT city, COUNT(*) AS cnt
FROM users
GROUP BY city
ORDER BY cnt DESC, city ASC;

七、总结

今天我们学习了MySQL聚合函数与分组查询的核心知识:

  1. 聚合函数:COUNT、SUM、AVG、MAX、MIN的使用方法和注意事项
  2. GROUP BY:单字段和多字段分组,以及分组后的数据筛选
  3. HAVING:分组后的过滤条件,与WHERE的区别
  4. WITH ROLLUP:生成分组小计和总计
  5. 实战应用:电商系统的订单量、销售额、用户统计

下一步预告

Day6:MySQL多表查询与JOIN

明天我们将学习多表查询的核心技术------JOIN。从INNER JOIN到LEFT JOIN,从自连接到UNION,你将掌握如何在多个表之间进行数据关联查询。这是实际工作中最常用的技能之一,敬请期待!


参考资料

MySQL 8.0 Reference Manual - Aggregate Functions


互动话题

  1. 你在使用GROUP BY时遇到过哪些坑?欢迎在评论区分享!
  2. 你们公司的数据分析场景主要用哪些聚合函数?
  3. 对于HAVING和WHERE的区别,你有什么独特的理解方式?

如果觉得本文对你有帮助,请点赞收藏!明天见!

相关推荐
Sharewinfo_BJ2 分钟前
Power BI沿袭视图详解:从看懂链路到做影响分析
数据库·数据分析·智信bi·上北智信
l1t5 分钟前
DeepSeek总结的 DuckDB 2.0 中更简洁的变量语法
数据库·sql
撸串-研究生6 分钟前
springboot福利院管理系统73979
java·spring boot·后端·福利院管理系统
xixiaoyunya19 分钟前
别再写一堆 if-else 了:用策略模式重构你的业务代码
后端
tachibana241 分钟前
Go 语言里的 Context 是什么?
开发语言·后端·golang·go·语言
sunshine8851 小时前
集成电路(IC)设计与封测行业10大常用ERP系统分析
大数据·数据库·人工智能
fen_fen1 小时前
Jmeter5.1.1连接oracle数据库的错误复盘
数据库·jmeter·oracle
鶴哥只手遮天1 小时前
Spring Framework 7.0.9 深度剖析-05-配置元数据解析:XML、注解与 JavaConfig
java·后端·开源
鶴哥只手遮天2 小时前
Spring Framework 7.0.9 深度剖析-16-异步与调度
java·后端·开源
高频因子挖掘机2 小时前
500只股票 × 多年日线:数据获取与本地存储如何分工?
后端·github·api