MySQL 聚合函数怎么用?

MySQL 的聚合函数不止标准 SQL 那五个,多了 GROUP_CONCAT、BIT_AND、STDDEV 这些。日常写报表、做统计,用对了能少写很多循环代码。

一、标准五个:COUNT、SUM、AVG、MAX、MIN

SELECT
  COUNT(*)             AS 订单数,
  SUM(amount)          AS 总额,
  AVG(amount)          AS 均价,
  MAX(amount)          AS 最大单,
  MIN(amount)          AS 最小单
FROM orders
WHERE status = 1;

NULL 的处理要记住:除了 COUNT(*),其余四个都会跳过 NULL。AVG 的分母是非 NULL 行数,SUM 全是 NULL 时返回 NULL。

二、GROUP_CONCAT:把多行拼成一列

这是 MySQL 特有的,做"每个用户买了哪些商品"这类列表特别好用:

SELECT user_id, GROUP_CONCAT(goods_name ORDER BY id SEPARATOR ',') AS goods
FROM orders
GROUP BY user_id;

默认分隔符是逗号,默认长度上限 1024 字节,超出会被截断且不报错。要拼接长文本必须先改:

SET SESSION group_concat_max_len = 102400;

这个坑很隐蔽——数据少的时候测不出问题,上线后用户订单多了就少半截。

三、COUNT(DISTINCT):去重统计

-- 有多少个不同的用户下过单
SELECT COUNT(DISTINCT user_id) FROM orders;

-- 每个商品有多少个不同买家
SELECT goods_id, COUNT(DISTINCT user_id) FROM orders GROUP BY goods_id;

去掉 DISTINCT 统计的是订单数,两者差很多。做 UV、活跃用户数时一定要加。

四、GROUP BY 与 HAVING 的执行顺序

SELECT user_id, SUM(amount) AS total
FROM orders
WHERE create_time >= '2026-01-01'
GROUP BY user_id
HAVING total > 1000
ORDER BY total DESC
LIMIT 10;

执行顺序是 FROM → WHERE → GROUP BY → 聚合 → HAVING → SELECT → ORDER BY → LIMIT。所以:

  • WHERE 里不能写聚合函数,会报 "Invalid use of group function"
  • HAVING 里可以引用 SELECT 的别名,WHERE 不行
  • WHERE 先过滤能减少分组量,比把条件全堆到 HAVING 里快得多

五、WITH ROLLUP:顺便出个合计行

SELECT category_id, COUNT(*) FROM goods GROUP BY category_id WITH ROLLUP;

结果最后多一行 category_id 为 NULL 的合计行。做报表时省得再查一次。注意 NULL 既是"合计"也可能是数据本身的分组值,前端展示要区分。

六、性能上的几点实话

  • GROUP BY 的字段建索引,能省掉临时表和文件排序。EXPLAIN 里看到 Using temporary; Using filesort 基本就是缺索引。
  • 大表 COUNT(*) 在 InnoDB 上要全表扫,几千万行级别会明显慢。可以用近似值 SHOW TABLE STATUS 的 Rows,或者单独维护计数表。
  • GROUP_CONCAT 在分组多、内容长的时候很吃内存,group_concat_max_len 别设得过大。
  • 聚合查询别 SELECT *,MySQL 会先取所有列再分组,白白多读数据。
  • ONLY_FULL_GROUP_BY 开启时,SELECT 里的非聚合列必须出现在 GROUP BY 中,否则报错。这是 MySQL 5.7 之后的默认行为,网上老教程的写法在新版本里跑不通。

七、一个综合例子

SELECT
  DATE(create_time)       AS dt,
  COUNT(*)                AS orders,
  COUNT(DISTINCT user_id) AS users,
  SUM(amount)             AS gmv,
  ROUND(AVG(amount), 2)   AS aov
FROM orders
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
GROUP BY DATE(create_time)
ORDER BY dt;

这是典型的日维度报表:订单量、下单人数、成交额、客单价。写熟了之后,大部分运营看板都能用这一套查出来。