GROUP BY 是分组统计的硬性前提,必须与 SELECT 中非聚合字段一致;COUNT(*) 统计所有行,COUNT(列) 仅统计该列非 NULL 值;WHERE 在分组前过滤,HAVING 在分组后过滤;LEFT JOIN 中应使用 COUNT(关联表主键) 避免空组被忽略。

GROUP BY 后必须跟 COUNT,不能只写 COUNT(*)
直接在 SELECT 里写 COUNT(*) 而不加 GROUP BY,结果只会返回一个总数,不是“每组”的条数。想按类别、日期、用户 ID 等分组统计,GROUP BY 是硬性前提。
常见错误是漏掉 GROUP BY 或写错字段名,比如:SELECT user_id, COUNT(*) FROM orders; 会报错(MySQL 5.7+ 严格模式下),因为 user_id 不在聚合函数里也不在 GROUP BY 中。
- 正确写法:
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id; -
GROUP BY字段必须和 SELECT 中非聚合列完全一致(如写了DATE(created_at),GROUP BY 也得写DATE(created_at)) - 如果 SELECT 里有多个非聚合字段,比如
status和region,GROUP BY必须同时包含它们:GROUP BY status, region
COUNT(column) 和 COUNT(*) 的行为差异很关键
COUNT(*) 统计所有行(包括 NULL 值所在行),而 COUNT(column) 只统计该列非 NULL 的行数。这个区别在处理可能含空值的字段时直接影响结果。
例如订单表中 refund_at 为 NULL 表示未退款,执行 COUNT(refund_at) 得到的是已退款订单数;用 COUNT(*) 再减去它,才是未退款数量。
- 统计“有评分的评论数”:用
COUNT(score)(跳过 score 为 NULL 的记录) - 统计“所有评论总数”:必须用
COUNT(*) -
COUNT(1)和COUNT(*)效果相同,但语义不清,建议统一用COUNT(*)
WHERE 和 HAVING 的过滤时机不同,别混用
WHERE 在分组前过滤行,HAVING 在分组后过滤组。想筛出“订单数超过 5 的用户”,必须用 HAVING COUNT(*) > 5,写在 WHERE 里会报错——因为 COUNT(*) 还没计算出来。
- 先过滤再分组:用
WHERE status = 'paid'(只统计已支付订单) - 先分组再筛选组:用
HAVING COUNT(*) >= 3(只保留下单 ≥3 次的用户) -
HAVING可以引用SELECT中的别名(如COUNT(*) AS cnt),但并非所有数据库都支持(PostgreSQL 支持,MySQL 8.0+ 支持,旧版 MySQL 不支持)
空组不显示?LEFT JOIN + COUNT 需注意 NULL 处理
用 LEFT JOIN 关联主表和从表后,若从表无匹配记录,COUNT(child.id) 返回 0,但 COUNT(*) 仍返回 1(因为主表行还在)。这是最容易出错的地方。
比如查每个分类下的商品数,分类表 categories 左连商品表 products,要得到“0 个商品的分类”,必须用 COUNT(products.id),而不是 COUNT(*)。
- 正确(含 0 计数):
SELECT c.name, COUNT(p.id) FROM categories c LEFT JOIN products p ON c.id = p.category_id GROUP BY c.id; - 错误(所有分类至少为 1):
COUNT(*)在 LEFT JOIN 后对主表行计数,掩盖了空关联 - 如果用了
COUNT(p.some_column)且该列允许 NULL,结果可能比预期少——优先选主键或非空字段
COUNT(*) 还是 COUNT(字段);分组字段和 SELECT 列必须对齐;过滤逻辑分清是行级还是组级——这些点卡住就查不出想要的结果。

















