用 GROUP BY + COUNT() 统计列值频次最直接:先按列分组再计数,必须写 GROUP BY,非聚合字段需全部列入;COUNT(*) 包含 NULL,COUNT(列名) 排除 NULL;求占比需窗口函数或子查询;加索引可大幅提升大数据量时性能。

用 GROUP BY + COUNT() 统计列值频次最直接
MySQL 里统计某列不同值的出现次数,核心就是 GROUP BY 搭配 COUNT()。它不是“查去重个数”,而是对每个唯一值分组后计数。比如想看 status 列里 'active'、'inactive' 各出现了几次,就得按该列分组。
常见错误是漏写 GROUP BY,只写 SELECT status, COUNT(*) —— 这在严格模式下会报错 ERROR 1140: In aggregated query without GROUP BY;即使能执行,结果也往往不符合预期(MySQL 5.7+ 默认启用 sql_mode=ONLY_FULL_GROUP_BY)。
- 必须确保
SELECT中所有非聚合字段都出现在GROUP BY子句里 - 如果要排序,加
ORDER BY COUNT(*) DESC能让高频值排前面 - 想过滤频次阈值(比如只看出现超 10 次的值),得用
HAVING COUNT(*) > 10,不能用WHERE
SELECT status, COUNT(*) AS cnt FROM users GROUP BY status ORDER BY cnt DESC;
处理 NULL 值时 COUNT() 和 COUNT(列名) 行为不同
COUNT(*) 统计所有行,包括该列值为 NULL 的记录;而 COUNT(status) 只统计 status 非 NULL 的行。如果你的业务中 NULL 是一种有效状态(比如“未设置”),那用 COUNT(*) 更准确;如果只想统计明确赋值的值,才用 COUNT(列名)。
-
COUNT(*):含NULL行,总数不变 -
COUNT(status):跳过status IS NULL的行 - 若需单独统计
NULL出现次数,可加条件:COUNT(*) FILTER (WHERE status IS NULL)(MySQL 不支持FILTER,得用SUM(IF(status IS NULL, 1, 0)))
想看占比?用窗口函数或子查询算百分比
纯 GROUP BY 只给绝对次数,要百分比得额外计算。MySQL 8.0+ 支持窗口函数,可避免子查询嵌套:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT status, COUNT(*) AS cnt, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) AS pct FROM users GROUP BY status;
MySQL 5.7 或更低版本没窗口函数,只能用子查询:
SELECT status, COUNT(*) AS cnt, ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM users), 2) AS pct FROM users GROUP BY status;
- 注意除数为 0 风险,实际使用建议加
IFNULL(..., 0)包裹子查询 - 窗口版性能通常更好,尤其表大时;子查询版逻辑直白但可能被重复执行
大数据量时 GROUP BY 性能容易卡住
当目标列基数高(比如用户邮箱)、且表行数上千万时,GROUP BY 可能触发临时表和文件排序,慢得明显。关键点不在语法,而在索引和执行计划:
- 给统计列建单列索引(如
INDEX(status))能显著加速分组过程 - 用
EXPLAIN FORMAT=TREE(8.0+)或EXPLAIN看是否用了Using temporary; Using filesort - 如果只是粗略估算,可考虑采样:
SELECT status, COUNT(*) FROM users TABLESAMPLE SYSTEM (1) GROUP BY status(MySQL 8.0.22+ 支持)
真正卡住的时候,往往不是写错语句,而是忘了索引或低估了数据分布——比如一列 99% 是同一个值,GROUP BY 仍要扫描全表确认。










