mysql 5.7+/postgresql的only_full_group_by要求select中非聚合列必须全部出现在group by中,否则报错;with rollup的null是语义占位符,非缺失值;多字段分组需匹配顺序的联合索引;where过滤行、having过滤组,不可混淆。

GROUP BY多字段必须严格匹配SELECT中的非聚合列
MySQL 5.7+ 和 PostgreSQL 默认启用 ONLY_FULL_GROUP_BY 模式,一旦 SELECT 列表里出现没套聚合函数的字段,而它又没完整出现在 GROUP BY 子句中,就会报错:Expression #X of SELECT list is not in GROUP BY clause。
这不是数据库“较真”,而是防止你拿到歧义结果。比如按 region 分组时,city 字段可能有几十个值,数据库没法决定该返回哪一个。
-
SELECT region, city, COUNT(*) FROM sales GROUP BY region, city—— 合法:所有非聚合列都出现在GROUP BY -
SELECT region, city, status, COUNT(*) FROM sales GROUP BY region, city—— 报错:status未分组也未聚合 -
SELECT UPPER(city) AS city_name, COUNT(*) FROM sales GROUP BY city—— 报错:别名不能替代原始表达式,得写成GROUP BY UPPER(city)
WITH ROLLUP生成小计/总计时,NULL行不是脏数据
GROUP BY region, city WITH ROLLUP 会自动插入带 NULL 的汇总行,但这些 NULL 是语义占位符,不是缺失值。例如:('East', NULL) 表示 East 区所有城市的合计,(NULL, NULL) 才是全表总计。
直接用 WHERE city IS NOT NULL 会误删小计行;也不能在 ORDER BY 里混用非分组字段,否则汇总行可能被插到中间,破坏层级结构。
- MySQL 8.0.12+ 推荐用
GROUPING()函数识别层级:IF(GROUPING(city), 'Region total', city) - 旧版本可用嵌套
IF:IF(city IS NULL AND region IS NOT NULL, 'Region subtotal', IF(region IS NULL, 'Grand total', city)) -
WITH ROLLUP必须紧跟GROUP BY列表后,不能加HAVING或其他修饰
多字段分组性能差,90%是因为索引没建对
单列索引对 GROUP BY a, b 基本无效。B+ 树索引依赖最左前缀匹配,分组字段顺序和索引字段顺序必须一致,否则无法跳过排序、直接利用索引完成分组。
更隐蔽的问题是字段上套函数:GROUP BY YEAR(order_date) 会让日期索引完全失效;高基数字段(如 user_id)混进 GROUP BY 列表,配合 WITH ROLLUP 可能爆炸出百万级组合行。
- 正确建联合索引:
CREATE INDEX idx_region_city ON sales(region, city) - 若查询带
WHERE region = '华东',该索引仍生效;但若只查WHERE city = '上海',索引就失效 - 避免在分组字段上用函数;高频场景可考虑添加生成列并建索引,如
ALTER TABLE sales ADD COLUMN order_year INT AS (YEAR(order_date)) STORED
HAVING和WHERE的分工不能颠倒
WHERE 在分组前过滤原始行,HAVING 在分组后过滤组。把聚合条件写进 WHERE 是常见错误,比如 WHERE COUNT(*) > 5 —— 此时还没分组,COUNT(*) 根本没意义,数据库直接拒绝执行。
真实业务中常要“先筛再分组”和“分组后再筛”两步结合,比如统计「近半年内订单数超10单的城市」:必须用 WHERE order_date >= '2026-04-01' 先缩小数据集,再用 HAVING COUNT(*) > 10 筛组。
- 错误写法:
SELECT city, COUNT(*) FROM orders WHERE COUNT(*) > 10 GROUP BY city - 正确写法:
SELECT city, COUNT(*) FROM orders WHERE order_date >= '2026-04-01' GROUP BY city HAVING COUNT(*) > 10 - 注意:如果
WHERE条件本身涉及聚合字段(如某字段平均值),只能拆成子查询或 CTE 处理
多维度汇总真正难的不是语法,而是理解每一层分组带来的粒度变化——加一个字段,可能让行数从几十暴增到几万;一个 NULL,可能代表小计而非脏数据;索引顺序错一位,查询就从毫秒变分钟。这些细节不写在报错信息里,但决定你能不能在 deadline 前跑出老板要的那张表。










