原创

MySQL数据查询中GROUP BY的用法与实战详解

什么是 GROUP BY

GROUP BY 是 MySQL 中最常用的分组查询语法,用来按照一个或多个字段对数据进行分组,然后通常配合聚合函数一起使用,比如:

  • COUNT():统计数量
  • SUM():求和
  • AVG():平均值
  • MAX():最大值
  • MIN():最小值

它解决的不是“查出每一行”,而是“按某种维度汇总数据”。

例如:

  • 按部门统计员工人数
  • 按用户统计订单数和消费总额
  • 按日期统计每天的访问量

GROUP BY 的核心作用

普通查询返回的是明细数据,而 GROUP BY 返回的是分组后的结果

看一个直观例子。假设有一张订单表:

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    status TINYINT NOT NULL,
    create_time DATETIME NOT NULL
);

插入几条数据:

INSERT INTO orders (user_id, product_name, amount, status, create_time) VALUES
(1, '键盘', 199.00, 1, '2026-04-01 10:00:00'),
(1, '鼠标', 99.00, 1, '2026-04-01 11:00:00'),
(2, '显示器', 1299.00, 1, '2026-04-02 09:30:00'),
(2, '耳机', 299.00, 0, '2026-04-02 15:00:00'),
(3, '笔记本支架', 159.00, 1, '2026-04-03 08:20:00');

如果直接查询:

SELECT * FROM orders;

返回的是每一条订单明细。

但如果按用户分组统计订单数:

SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id;

结果含义就变成了:每个用户有多少笔订单。


GROUP BY 的基本语法

SELECT 分组字段, 聚合函数
FROM 表名
WHERE 条件
GROUP BY 分组字段
HAVING 分组后条件
ORDER BY 排序字段;

执行顺序通常可以理解为:

  1. FROM
  2. WHERE
  3. GROUP BY
  4. HAVING
  5. SELECT
  6. ORDER BY
  7. LIMIT

这里最重要的是两个点:

  • WHERE分组前过滤
  • HAVING分组后过滤

单字段分组

按用户统计订单数

SELECT user_id, COUNT(*) AS total_orders
FROM orders
GROUP BY user_id;

按订单状态统计数量

SELECT status, COUNT(*) AS total
FROM orders
GROUP BY status;

这个查询的意义是:把 status 相同的数据归为一组,然后统计每组的记录数。


多字段分组

GROUP BY 不只能按一个字段分组,也可以按多个字段组合分组。

按用户和状态分组统计订单数

SELECT user_id, status, COUNT(*) AS total
FROM orders
GROUP BY user_id, status;

这和只按 user_id 分组完全不同。

  • GROUP BY user_id:每个用户一组
  • GROUP BY user_id, status:每个用户的每种状态分别一组

也就是说,分组粒度更细。


常见聚合函数与 GROUP BY 配合使用

1. COUNT()

统计数量:

SELECT user_id, COUNT(*) AS total_orders
FROM orders
GROUP BY user_id;

2. SUM()

统计总金额:

SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id;

3. AVG()

统计平均订单金额:

SELECT user_id, AVG(amount) AS avg_amount
FROM orders
GROUP BY user_id;

4. MAX() / MIN()

统计最大值和最小值:

SELECT user_id, MAX(amount) AS max_amount, MIN(amount) AS min_amount
FROM orders
GROUP BY user_id;

5. 多个聚合函数一起使用

SELECT
    user_id,
    COUNT(*) AS total_orders,
    SUM(amount) AS total_amount,
    AVG(amount) AS avg_amount,
    MAX(amount) AS max_amount,
    MIN(amount) AS min_amount
FROM orders
GROUP BY user_id;

这类写法在报表统计中非常常见。


WHEREHAVING 的区别

这是 GROUP BY 最容易混淆的地方之一。

WHERE:分组前过滤

比如只统计已支付订单:

SELECT user_id, COUNT(*) AS total_orders
FROM orders
WHERE status = 1
GROUP BY user_id;

这里先把 status = 1 的数据筛出来,再进行分组。

HAVING:分组后过滤

比如只保留订单数大于 1 的用户:

SELECT user_id, COUNT(*) AS total_orders
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 1;

这里先分组,再对分组结果进行筛选。

一起使用的场景

SELECT user_id, SUM(amount) AS total_amount
FROM orders
WHERE status = 1
GROUP BY user_id
HAVING SUM(amount) > 200;

含义是:

  1. 先筛选已支付订单
  2. 再按用户分组
  3. 最后只保留消费总额大于 200 的用户

一句话区分

  • WHERE 过滤的是原始记录
  • HAVING 过滤的是分组结果

GROUP BYORDER BY 结合使用

分组查询后,通常还需要排序。

按用户订单总金额倒序排列

SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
ORDER BY total_amount DESC;

这里 ORDER BY 排的是聚合后的结果。

也可以直接写聚合表达式:

SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
ORDER BY SUM(amount) DESC;

通常使用别名更清晰。


GROUP BY 与日期统计

业务里经常需要按天、按月、按年做统计。

按天统计订单数量

SELECT DATE(create_time) AS order_date, COUNT(*) AS total_orders
FROM orders
GROUP BY DATE(create_time)
ORDER BY order_date ASC;

按月统计订单总金额

SELECT DATE_FORMAT(create_time, '%Y-%m') AS order_month, SUM(amount) AS total_amount
FROM orders
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
ORDER BY order_month ASC;

这种写法很常见,但要注意一个问题:对字段使用函数可能影响索引利用率。如果数据量大,统计类查询要结合实际场景优化。


GROUP BY 查询时为什么有时报错

最常见的报错和这条 SQL 有关:

SELECT user_id, product_name, COUNT(*)
FROM orders
GROUP BY user_id;

很多人会问:为什么这样写有时能执行,有时会报错?

原因在于:product_name 既没有出现在 GROUP BY 中,也没有被聚合函数包裹。

为什么这是个问题

user_id 分组后,一个用户可能对应多条订单、多个 product_name。 那最终结果里 product_name 应该显示哪一个?

从逻辑上说,这是不确定的。

MySQL 5.7+ 中常见现象

如果开启了 ONLY_FULL_GROUP_BY,这类 SQL 会直接报错。因为它不符合标准 SQL 语义。

正确写法通常有三种:

写法一:把字段加入分组条件

SELECT user_id, product_name, COUNT(*)
FROM orders
GROUP BY user_id, product_name;

写法二:对字段使用聚合函数

SELECT user_id, MAX(product_name) AS product_name, COUNT(*) AS total
FROM orders
GROUP BY user_id;

写法三:明确知道业务语义时使用 ANY_VALUE()

SELECT user_id, ANY_VALUE(product_name) AS product_name, COUNT(*) AS total
FROM orders
GROUP BY user_id;

但要注意,ANY_VALUE() 只是告诉 MySQL:这个字段取组内任意一个值即可。 它不会保证业务语义一定正确,所以不能滥用。


ONLY_FULL_GROUP_BY 到底是什么

它是 MySQL 的一种 SQL 模式,用来限制不规范的分组查询。

开启后,SELECT 中出现的非聚合字段,必须满足以下条件之一:

  • 出现在 GROUP BY
  • 被聚合函数包裹
  • 能被 MySQL 判断为函数依赖字段

例如主键依赖场景下,某些查询是允许的。但在日常开发里,不要依赖这种边界行为,最稳妥的方式还是写出语义明确的 SQL


GROUP BY 的典型业务场景

按部门统计员工人数

CREATE TABLE employee (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(50) NOT NULL,
    dept_name VARCHAR(50) NOT NULL,
    salary DECIMAL(10,2) NOT NULL,
    hire_date DATE NOT NULL
);
SELECT dept_name, COUNT(*) AS emp_count
FROM employee
GROUP BY dept_name;

按部门统计平均薪资

SELECT dept_name, AVG(salary) AS avg_salary
FROM employee
GROUP BY dept_name;

按商品统计销量

CREATE TABLE order_item (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    quantity INT NOT NULL,
    price DECIMAL(10,2) NOT NULL
);
SELECT product_id, product_name, SUM(quantity) AS total_quantity
FROM order_item
GROUP BY product_id, product_name
ORDER BY total_quantity DESC;

查找重复数据

GROUP BY 还有一个高频用途:查重。

例如查找重复手机号:

CREATE TABLE user_info (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_name VARCHAR(50) NOT NULL,
    phone VARCHAR(20) NOT NULL,
    email VARCHAR(100) DEFAULT NULL
);
SELECT phone, COUNT(*) AS repeat_count
FROM user_info
GROUP BY phone
HAVING COUNT(*) > 1;

这类 SQL 很适合数据治理和清洗场景。


GROUP BYDISTINCT 的区别

很多人刚接触时会把 GROUP BYDISTINCT 混在一起,但它们用途并不相同。

DISTINCT:去重

SELECT DISTINCT status
FROM orders;

作用是去掉重复值,返回唯一的 status

GROUP BY:分组汇总

SELECT status, COUNT(*) AS total
FROM orders
GROUP BY status;

作用是按 status 分组后,再做统计。

什么时候可以互相替代

在某些简单场景下:

SELECT DISTINCT status FROM orders;

SELECT status FROM orders GROUP BY status;

结果可能一样。

但语义上:

  • DISTINCT 强调去重
  • GROUP BY 强调分组统计

在需要聚合计算时,应该优先想到 GROUP BY


GROUP BY 的性能注意事项

GROUP BY 本质上不是“慢”,但在数据量大时,确实容易成为性能瓶颈。

1. 分组字段最好有合适索引

例如:

SELECT user_id, COUNT(*)
FROM orders
GROUP BY user_id;

如果 user_id 上有索引,执行效率通常更高。

CREATE INDEX idx_user_id ON orders(user_id);

2. 避免对索引字段做函数运算后再分组

例如:

SELECT DATE(create_time), COUNT(*)
FROM orders
GROUP BY DATE(create_time);

这类写法可读性高,但可能导致索引无法被高效利用。

如果查询量很大,可以考虑:

  • 通过范围条件先过滤
  • 增加冗余统计字段
  • 做离线汇总表
  • 使用物化统计方案

3. 先过滤再分组

能放在 WHERE 的条件,不要放到 HAVING

例如:

不推荐:

SELECT user_id, COUNT(*)
FROM orders
GROUP BY user_id
HAVING user_id > 100;

更推荐:

SELECT user_id, COUNT(*)
FROM orders
WHERE user_id > 100
GROUP BY user_id;

因为 WHERE 会在分组前减少参与计算的数据量。

4. 分组字段不要过多、过杂

分组维度越多,分组结果越细,开销通常越大。

例如:

GROUP BY user_id, status, product_name, DATE(create_time)

这类语句未必有问题,但要确认是否真的需要这么细的统计粒度。

5. 明细查询和统计查询分离

很多系统把列表页查询和报表统计混在一起,导致 SQL 又长又慢。 对于高并发业务,通常建议:

  • 明细查询单独写
  • 聚合统计单独写
  • 重报表做异步汇总或缓存

GROUP BY 的几个高频误区

误区一:GROUP BY 只是去重

不是。 它虽然能表现出类似去重的效果,但本质是分组,核心价值在于配合聚合函数做汇总分析。

误区二:分组后可以随便查任意字段

不行。 没有参与分组、也没有聚合的字段,在严格 SQL 模式下通常不允许直接出现在 SELECT 中。

误区三:HAVING 可以替代 WHERE

不推荐。 虽然某些查询能写出来,但语义和性能都不如 WHERE 合适。

误区四:COUNT(字段)COUNT(*) 完全一样

不完全一样。

  • COUNT(*):统计记录数
  • COUNT(column):统计该列不为 NULL 的数量

例如:

SELECT COUNT(*) AS total_rows, COUNT(email) AS non_null_email
FROM user_info;

如果 email 有空值,这两个结果就不同。


实战示例:一条完整的分组统计 SQL

需求:统计每个用户已支付订单的数量、总金额、平均金额,并按总金额倒序排列,只保留总金额大于 200 的用户。

SELECT
    user_id,
    COUNT(*) AS total_orders,
    SUM(amount) AS total_amount,
    AVG(amount) AS avg_amount
FROM orders
WHERE status = 1
GROUP BY user_id
HAVING SUM(amount) > 200
ORDER BY total_amount DESC;

这条 SQL 基本覆盖了 GROUP BY 的核心用法:

  • WHERE:分组前过滤
  • GROUP BY:按用户分组
  • 聚合函数:统计数量、总金额、平均值
  • HAVING:过滤分组结果
  • ORDER BY:对统计结果排序

能真正理解这条 SQL,GROUP BY 的核心就算掌握了。


开发中如何写出更稳妥的 GROUP BY

可以遵循这几个原则:

  1. 先想清楚“按什么维度统计”
  2. 再决定“每组要算什么指标”
  3. 非分组字段不要直接放进 SELECT
  4. 尽量把过滤条件放进 WHERE
  5. 对大表统计,优先考虑索引和汇总方案
  6. 在 MySQL 5.7+ 环境下,注意 ONLY_FULL_GROUP_BY 对 SQL 合法性的影响

总结

GROUP BY 的本质是:把多行数据按指定字段归并成多个组,再对每个组进行统计计算

掌握它时,最关键的不是死记语法,而是理解这几个问题:

  • 分组维度是什么
  • 每个组要统计什么
  • 哪些条件属于分组前过滤,哪些属于分组后过滤
  • 哪些字段可以出现在 SELECT
  • 大数据量下如何控制性能

只要这几个点清楚了,GROUP BY 就不只是“会用”,而是真正能在业务查询、报表统计、数据分析和排查问题中用得稳、用得准。

正文到此结束
评论插件初始化中...
Loading...