mysql如何进行分组查询_mysql group by语法解析

来源:这里教程网 时间:2026-02-28 20:46:04 作者:

GROUP BY 后必须出现 SELECT 中的所有非聚合字段

MySQL 8.0+ 默认启用

sql_mode=ONLY_FULL_GROUP_BY
,这意味着如果
SELECT
列表里有非聚合字段(比如
name
、
status
),它们必须显式出现在
GROUP BY
子句中,否则报错:
Expression #1 of SELECT list is not in GROUP BY clause
。

常见错误写法:

SELECT id, name, COUNT(*) FROM users GROUP BY status;

这在 MySQL 5.7 之前可能“侥幸”通过,但结果不可靠——

id
和
name
取的是哪一行的值?MySQL 不保证。8.0+ 直接拒绝执行。

正确做法:把所有非聚合列都放进
GROUP BY
,例如
GROUP BY status, name
或改用聚合函数包裹,如
MAX(id)
、
ANY_VALUE(name)
(需确认业务逻辑是否允许)
不建议临时关掉
ONLY_FULL_GROUP_BY
,它是为了防止隐式歧义而设的

WHERE 和 HAVING 的分工不能颠倒

WHERE
过滤行,
HAVING
过滤分组,这是关键区别。很多人误把条件全塞进
HAVING
,导致性能下降甚至逻辑错误。

比如查“每种状态的用户数超过 5 人的活跃用户”:

SELECT status, COUNT(*) AS cnt
FROM users 
WHERE is_active = 1
GROUP BY status
HAVING cnt > 5;
is_active = 1
必须放
WHERE
:提前过滤,减少参与分组的数据量
cnt > 5
必须放
HAVING
:因为
cnt
是分组后才计算出的别名,
WHERE
看不见它
若把
cnt > 5
错写成
WHERE COUNT(*) > 5
,会直接报错:
Invalid use of group function

ORDER BY 中引用聚合结果要小心别名和字段位置

GROUP BY
查询中,
ORDER BY
可以用列别名、序号或表达式,但不同 MySQL 版本行为略有差异。

例如:

SELECT status, COUNT(*) AS total FROM users GROUP BY status ORDER BY total DESC;
用别名
total
排序是安全且可读的,推荐
用位置序号如
ORDER BY 2
虽然合法,但一旦调整
SELECT
列顺序就容易出错
避免在
ORDER BY
中重复写聚合表达式,比如
ORDER BY COUNT(*)
,虽然可行,但冗余且难维护
注意:MySQL 允许
ORDER BY
引用未出现在
SELECT
中的字段(只要在
GROUP BY
里),但这种写法依赖隐式分组语义,易引发理解偏差

多字段分组时注意 NULL 值的分组行为

当

GROUP BY
包含多个字段,且其中某些值为
NULL
,MySQL 把所有
NULL
视为“相同”,归入同一组。这和
WHERE col = NULL
永远为 false 不同,但容易被忽略。

例如:

SELECT dept, level, COUNT(*) FROM employees GROUP BY dept, level;
如果
dept
或
level
有
NULL
,它们各自形成独立的
NULL
组(即
(NULL, 'senior')
、
('tech', NULL)
、
(NULL, NULL)
是三个不同组)
但单个字段为
NULL
的所有行,会全部聚到该字段对应的那个
NULL
组里
若想排除
NULL
再分组,得加
WHERE dept IS NOT NULL AND level IS NOT NULL
用
COALESCE(dept, 'unknown')
替换
NULL
是常见补救手段,但要注意它改变了原始分组语义
实际写
GROUP BY
时,最常卡住的地方不是语法,而是没想清楚“我要按什么维度聚合”和“这个维度的空值代表什么”。这两个问题没理清,后面加再多
HAVING
或
ORDER BY
都只是在掩盖逻辑漏洞。

相关推荐