count(*)统计所有行(含null),count(字段)跳过null;group by需与select非聚合字段一致;where过滤行,having过滤分组结果;状态字段需规范类型与值以保证分组精度。

count(*) 和 count(字段) 在统计状态时结果可能不同
直接写 SELECT status, COUNT(*) FROM orders GROUP BY status 是最常用做法,但要注意:如果 status 字段允许为 NULL,COUNT(status) 会跳过这些行,而 COUNT(*) 不会。实际业务里,“未设置状态”常被存为 NULL,这时用 COUNT(*) 才能体现“这一组有多少条记录”,而不是“有多少条非空状态记录”。
常见错误现象:COUNT(status) 返回 0 条,但肉眼可见表里有几十条 status IS NULL 的数据。
- 统计总数(含 NULL)→ 用
COUNT(*) - 只统计明确赋值的状态 → 用
COUNT(status),但得先确认业务是否真要排除 NULL - 想把 NULL 单独归为一类统计?加
COALESCE(status, 'unknown')再分组
GROUP BY 后没选非聚合字段会报错(ONLY_FULL_GROUP_BY)
MySQL 5.7+ 默认开启 ONLY_FULL_GROUP_BY 模式,如果写 SELECT id, status, COUNT(*) FROM orders GROUP BY status,会直接报错:Expression #1 of SELECT list is not in GROUP BY clause。这不是语法错,是 SQL 标准强制要求:SELECT 中每个非聚合字段都必须出现在 GROUP BY 里。
使用场景:你想看每个状态对应的第一条订单 ID?那得用子查询或窗口函数,不能靠 GROUP BY 硬拖。
- 只想看状态和数量 → 只写
SELECT status, COUNT(*) - 需要关联其他字段(比如最新创建时间)→ 改用
MAX(created_at)这类聚合函数 - 真要取某一行完整数据 → 别硬套 GROUP BY,考虑
ROW_NUMBER() OVER (PARTITION BY status ORDER BY created_at DESC)
状态字段类型影响分组精度(字符串 vs 枚举 vs 数字)
如果 status 是 VARCHAR 类型,但值里混着空格、大小写不一致(比如 'pending' 和 'Pending'),GROUP BY 会当成两个不同状态。数字型状态(如 0/1/2)看似安全,但如果前端传参没校验,可能存进字符串 '1 '(带空格),同样导致重复分组。
性能影响:对 status 建索引能加速 GROUP BY,但前提是字段选择性高;如果只有 3 种状态,索引收益有限,反而增加写入开销。
- 查出异常分组:用
SELECT CONCAT('[', status, ']'), COUNT(*) FROM orders GROUP BY status看方括号里有没有空格或不可见字符 - 统一清洗:建表时用
ENUM('pending','paid','shipped')或TINYINT+ 应用层映射更可靠 - 临时修复:分组前用
TRIM(UPPER(status)),但别长期依赖
WHERE 和 HAVING 的位置不能颠倒
想统计“已支付且完成发货”的状态数量?写成 SELECT status, COUNT(*) FROM orders GROUP BY status HAVING status = 'shipped' 是错的——HAVING 是在分组后筛组,不是筛原始行。如果表里有 1000 条 status = 'pending',它们会被聚成 1 行,再被 HAVING 干掉,但你真正想排除的是这些 pending 记录本身。
正确顺序:先用 WHERE 过滤原始数据(比如 WHERE status IN ('paid', 'shipped')),再 GROUP BY,最后用 HAVING 对聚合结果做二次筛选(比如“只显示数量 > 100 的状态”)。
- 过滤单条记录 → 用
WHERE - 过滤分组结果(比如“找出订单数超 50 的状态”)→ 用
HAVING COUNT(*) > 50 -
HAVING里不能用未聚合的字段(如HAVING created_at > '2024-01-01'会报错)











