mysql 5.7+和postgresql要求select中非聚合字段必须出现在group by中或用聚合函数包裹,否则报错;count(*)统计所有行,count(列名)仅统计该列非null值;空组不会出现在group by结果中,需用left join补全。

GROUP BY 后漏写非聚合字段会报错
MySQL 5.7+ 和 PostgreSQL 默认不允许 SELECT 列表里出现未出现在 GROUP BY 中、又没被聚合函数包裹的字段。比如写 SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id,name 没参与分组也没聚合,MySQL 会直接报 Expression #2 of SELECT list is not in GROUP BY clause。
常见做法是:要么把 name 加进 GROUP BY,要么确认它和 user_id 是一对一关系后用 ANY_VALUE(name)(MySQL)或 MIN(name)(通用)兜底。
- PostgreSQL 更严格,不支持
ANY_VALUE,必须显式处理或改用子查询 - SQLite 默认宽松,但开启
PRAGMA legacy_alter_table = ON后行为可能变化,别依赖默认宽容 - 如果只是想查每个用户订单数,就只选
user_id和COUNT(*),别多带无关字段
COUNT(*) vs COUNT(列名) 行为差异很大
COUNT(*) 统计行数,包括所有行,哪怕整行都是 NULL;COUNT(列名) 只统计该列非 NULL 的值。比如 COUNT(email) 会跳过 email IS NULL 的记录。
实际场景中容易踩坑:想统计“有手机号的用户数”,却写了 COUNT(*),结果把没填手机号的也算了进去。
- 统计总记录数用
COUNT(*),性能通常最好(优化器可走索引行数估算) - 统计某字段有效值数量,必须用
COUNT(字段名),不能靠WHERE 字段名 IS NOT NULL再套COUNT(*)—— 逻辑等价但可读性差,且 WHERE 过滤后丢失分组维度信息 -
COUNT(1)和COUNT(*)在绝大多数引擎里没区别,别迷信“1 更快”
空组(无匹配数据)不会出现在 GROUP BY 结果里
GROUP BY 只返回满足 WHERE 条件的分组。比如查“每个部门的平均薪资”,但某个部门没人,那结果里根本不会出现这个部门的行 —— 不会显示 dept_name | NULL 或 0。
需要补全空组时,得主动用 LEFT JOIN 或 RIGHT JOIN 把部门表作为主表,再关联统计表:
SELECT d.name, COALESCE(AVG(e.salary), 0) AS avg_salary FROM departments d LEFT JOIN employees e ON d.id = e.dept_id GROUP BY d.id, d.name
-
COALESCE是必须的,否则AVG()在空组里返回NULL,不是0 -
GROUP BY里要把d.id和d.name都写上,避免 MySQL 严格模式报错 - 别试图用
HAVING COUNT(*) = 0找空组 ——HAVING是对已分组结果过滤,空组压根没生成
在 WHERE 和 HAVING 之间选错条件会导致逻辑错误
WHERE 过滤的是原始行,HAVING 过滤的是分组后的聚合结果。比如“查订单数超过 5 的用户”,条件 COUNT(*) > 5 必须写在 HAVING 里;如果误写进 WHERE,SQL 直接报错 —— 因为 WHERE 阶段还不存在 COUNT(*)。
另一个典型错误:把本该在 WHERE 过滤的条件挪到 HAVING,比如“查 2023 年之后下单的用户中,订单数超 5 的人”。若把 order_date > '2023-01-01' 放 HAVING,会先按用户分组、再过滤,导致统计范围变大(包含历史订单),结果错误。
-
WHERE用于单行判断(字段值、时间范围、状态码等) -
HAVING只用于聚合结果判断(COUNT、SUM、AVG等表达式) - 有些数据库(如旧版 SQLite)允许
HAVING里用非聚合字段,但这是非标准行为,别依赖
GROUP BY 的字段顺序不影响结果正确性,但会影响排序稳定性 —— 如果没加 ORDER BY,不同数据库返回顺序可能不同,别假设它按 GROUP BY 顺序排。










