在MySQL的数据查询场景中,分组统计是非常高频的操作,核心需求是先按照指定字段对数据分组,再计算每个分组内的记录总数。要实现这个效果,需要结合GROUP BY子句和COUNT函数共同完成。

基础分组统计方法
最基础的分组统计逻辑是先用GROUP BY指定分组的字段,再用COUNT()函数统计每个分组的记录数。比如我们有一张用户订单表user_order,结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| order_id | int | 订单ID,主键 |
| user_id | int | 用户ID |
| order_status | varchar | 订单状态 |
| create_time | datetime | 订单创建时间 |
如果要统计每个用户的订单数量,对应的SQL语句如下:
-- 按照user_id分组,统计每个用户的订单条数 SELECT user_id, COUNT(*) AS order_count FROM user_order GROUP BY user_id;
这里的COUNT(*)会统计每个分组内所有行的数量,不管行内的字段是否有null值,都会计入总数。
COUNT不同参数的区别
COUNT函数有三种常见的参数形式,在分组统计时结果会有差异:
COUNT(*):统计分组内的总行数,包含null值的行。COUNT(字段名):统计指定字段非null的行数,如果字段值为null则不计入。COUNT(DISTINCT 字段名):统计指定字段去重后非null的行数。
比如要统计每个用户的**有效订单**数量,订单状态为null的不计入,就可以使用COUNT(order_status):
-- 统计每个用户有效订单(order_status不为null)的数量 SELECT user_id, COUNT(order_status) AS valid_order_count FROM user_order GROUP BY user_id;
如果要统计每个用户的**不同订单状态数量**,就需要用COUNT(DISTINCT order_status):
-- 统计每个用户有多少种不同的订单状态 SELECT user_id, COUNT(DISTINCT order_status) AS status_type_count FROM user_order GROUP BY user_id;
带条件的分组统计
如果需要在分组统计前先过滤数据,或者分组后过滤统计结果,需要区分WHERE和HAVING子句的使用场景:
分组前过滤:使用WHERE
如果只需要统计2024年之后的订单数量,需要先过滤创建时间,此时用WHERE子句,在GROUP BY之前执行:
-- 统计每个用户2024年之后的订单数量 SELECT user_id, COUNT(*) AS order_count FROM user_order WHERE create_time >= '2024-01-01 00:00:00' GROUP BY user_id;
分组后过滤:使用HAVING
如果需要统计订单数量大于5的用户,此时过滤的是分组后的统计结果,需要用HAVING子句,在GROUP BY之后执行:
-- 统计订单数量大于5的用户 SELECT user_id, COUNT(*) AS order_count FROM user_order GROUP BY user_id HAVING order_count > 5;
多字段分组统计
如果需要按照多个字段分组统计,比如同时按照用户ID和订单状态统计每个状态下的订单数量,只需要在GROUP BY后面添加多个字段即可:
-- 按照用户ID和订单状态分组,统计每个用户每个状态的订单数量 SELECT user_id, order_status, COUNT(*) AS status_order_count FROM user_order GROUP BY user_id, order_status;
多字段分组时,只有所有分组字段的值都相同的记录,才会被分到同一个组里。
常见注意事项
- SELECT子句中出现的非聚合字段,必须全部出现在GROUP BY子句中,否则MySQL可能会返回不确定的结果,或者在严格模式下直接报错。
- 如果分组字段有null值,所有null值的记录会被分到同一个组里。
- COUNT函数统计时,空字符串和0值不属于null,会被正常计入总数。
分组统计是MySQL数据分析的基础操作,掌握GROUP BY和COUNT的配合逻辑,以及不同子句的使用场景,就能应对绝大多数分组统计的需求。