应优先使用 COUNT(*) 配合 GROUP BY 统计行数,避免混入未聚合字段、误用 COUNT(列名) 或 COUNT(1),注意 NULL 处理、WHERE/HAVING 区分及窗口函数替代子查询提升性能。

直接用 COUNT(*) 配合 GROUP BY 就能拿到每个分类的行数,但写错字段或忽略 NULL 会导致结果偏差。
SELECT 中只放分组字段和聚合结果
常见错误是 SELECT 里混入未聚合又没出现在 GROUP BY 中的字段,比如:
SELECT category, name, COUNT(*) FROM products GROUP BY category;
这在 MySQL 5.7+ 或 PostgreSQL 中会报错(sql_mode=only_full_group_by 开启时)。原因:name 每组可能有多个值,数据库不知道该取哪一个。
- 正确写法:只保留分组字段 + 聚合函数,如
SELECT category, COUNT(*) AS cnt FROM products GROUP BY category - 如果真要带某条记录的字段(如最新商品名),得用子查询或窗口函数,不能靠 GROUP BY “顺手挑”
-
COUNT(*)是默认推荐,它统计所有行,包括category为 NULL 的行;若想排除 NULL 分类,加HAVING category IS NOT NULL或改用COUNT(category)
多字段分组时 NULL 会被单独归为一组
比如按 category 和 sub_category 分组,(NULL, 'A')、('B', NULL)、(NULL, NULL) 都算不同分组——这常不符合业务预期。
- 想把 NULL 统一当作“未知”,提前用
COALESCE(category, 'unknown')转换 - 若字段允许 NULL 且你希望占比计算中分母不含 NULL,分母就得用
COUNT(category) OVER()而非COUNT(*) OVER() - 组合分组后结果集容易稀疏,加
ORDER BY COUNT(*) DESC可快速识别高频组合
避免 COUNT(列名) 误用导致漏计
COUNT(category) 和 COUNT(*) 看似只差个括号,语义完全不同:
-
COUNT(*):统计整行,哪怕category是 NULL 也计 1 -
COUNT(category):只统计category非 NULL 的行,NULL 行直接跳过 - 多数场景下应优先用
COUNT(*);只有明确要排除空值时才用COUNT(列名) - 别用
COUNT(1)—— 它和COUNT(*)执行效率几乎一样,但可读性差,没必要
WHERE 和 HAVING 别写反位置
筛选逻辑放在哪一层,决定结果是否包含空分类:
-
WHERE category IS NOT NULL:分组前过滤,NULL 分类彻底消失 -
HAVING COUNT(*) > 0:没意义,每组至少一行;真正有用的是HAVING COUNT(*) > 5这类对聚合结果的约束 - 想查“2024 年每个分类的订单数”,时间条件必须写在 JOIN 的
ON子句里(如果是关联查询),否则WHERE created_at >= '2024-01-01'会把没订单的分类也过滤掉
最易被忽略的点是:分组后数据形态已经变了,你不能再把它当原始表用。比如想补一个“总行数”字段,不能在 GROUP BY 后硬除,得靠窗口函数或子查询;而一旦用了子查询,就得盯紧它的执行次数——大表上反复查总数,性能可能比窗口函数差一截。

















