必须用count(*)而非count(字段名),因为前者统计每组总行数(含null),后者仅统计该字段非null行;若字段允许为空,用count(字段名)会漏计null所在行,导致结果偏少。

直接用 COUNT(*) 配 GROUP BY 字段名 就能查出每个值出现几次,别用 COUNT(字段名) 替代——它会跳过 NULL,导致漏数。
为什么必须用 COUNT(*) 而不是 COUNT(字段名)
COUNT(*) 统计的是每组的总行数,包括该字段为 NULL 的行;COUNT(字段名) 只统计该字段非 NULL 的行。如果字段允许为空,而你又想准确知道“这个值(含空)一共出现了多少次”,用后者会少算。
- 比如查
email重复次数,表里有 10 条email IS NULL的记录:用COUNT(email)得到的是 0,但实际这 10 行在GROUP BY email下会被归为一组,COUNT(*)才返回 10 - 业务上若把
NULL当作“未填写”,你可能需要单独看到这一组,而不是让它被忽略 - MySQL、PostgreSQL 默认都把所有
NULL视为相同值分到同一组,这点不能靠COUNT(字段名)掩盖
GROUP BY 后加 HAVING 是为了筛分组,不是筛原始数据
HAVING 是在分组完成之后对“组”做条件过滤,和 WHERE 完全不同层级。常见错误是拿 HAVING 当 WHERE 用,或者漏写 GROUP BY 就直接写 HAVING。
- 想查出现超过 5 次的
status值:SELECT status, COUNT(*) FROM orders GROUP BY status HAVING COUNT(*) > 5 - 错写成
WHERE COUNT(*) > 5→ 直接报错:ERROR 1111 (HY000): Invalid use of group function - 只写
HAVING COUNT(*) > 1却没写GROUP BY status→ MySQL 报错或返回空,PostgreSQL 直接拒绝执行 - 性能提示:能用
WHERE先缩小数据集(如WHERE created_at > '2024-01-01'),就别全量分组后再HAVING过滤
查单个具体值的出现次数,根本不需要 GROUP BY
如果只关心某一个值(比如 status = 'active')出现了几次,硬套 GROUP BY + HAVING 是低效且易错的冗余操作。
- 正确写法:
SELECT COUNT(*) FROM table_name WHERE status = 'active' - 错误写法:
SELECT status, COUNT(*) FROM table_name GROUP BY status HAVING status = 'active'—— 多扫全表、多分组、语义混乱 - 确保
WHERE条件字段有索引,尤其是大表,否则全表扫描代价很高 - 注意大小写和空格:默认 collation 下
'Alice '和'alice'可能不等价,必要时用UPPER()或建函数索引
多字段组合重复时,GROUP BY 必须写全,NULL 行为要主动控制
按多个字段判断是否重复(如 user_id + order_date),GROUP BY 必须包含全部目标字段;NULL 在各字段中如何参与分组,得根据业务逻辑显式处理。
-
GROUP BY user_id, order_date和GROUP BY order_date, user_id效果完全一样,顺序无关 - 若任一字段为
NULL,整行仍会被归入对应组合组(如(123, NULL)和(123, NULL)算重复) - 不想让
NULL参与判重:加WHERE user_id IS NOT NULL AND order_date IS NOT NULL - 想把
NULL显式转成占位符便于识别:GROUP BY COALESCE(user_id, -1), COALESCE(order_date, '1970-01-01') - MySQL 8.0+ 支持
GROUP BY user_id IS NULL, user_id把NULL和非NULL拆开,但多数场景没必要这么绕
真正容易卡住的点,往往不是语法写错,而是没意识到 NULL 已经悄悄被聚成一组,或者以为 COUNT(字段名) 和 COUNT(*) 只是风格差异——它们在重复统计这件事上,结果可能差好几倍。










