group by 必须严格匹配 select 中所有非聚合列,不能使用别名、函数包装或省略复合主键字段;null 分组行为因数据库而异;索引顺序须与 group by 一致才能生效;多列匹配 where (a,b)=() 与 group by a,b 无关。

GROUP BY 必须与复合主键字段完全一致
复合主键本身不改变 GROUP BY 逻辑,但容易让人误以为“主键自动可用”——其实它只是约束,不是语法糖。只要你在 SELECT 中写了非聚合列,这些列就必须原样出现在 GROUP BY 中,不能缩写、不能别名、不能加函数包装。
比如表 enrollment 的复合主键是 (student_id, course_id),你想统计每个学生每门课的选课次数:
SELECT student_id, course_id, COUNT(*) AS cnt FROM enrollment GROUP BY student_id, course_id;
这合法;但下面这些写法都会出错:
-
SELECT s_id, c_id, COUNT(*) ... GROUP BY student_id, course_id(别名s_id不行) -
SELECT TRIM(student_id), course_id, COUNT(*) ... GROUP BY student_id, course_id(TRIM()包装后不等价) -
SELECT student_id, COUNT(*) ... GROUP BY student_id(漏了course_id,即使它是主键一部分)
多列分组时 NULL 值行为要特别小心
复合主键字段允许为 NULL(除非显式定义为 NOT NULL),而不同数据库对 NULL = NULL 在分组中的处理不一致。PostgreSQL 和 MySQL 8.0+ 默认把多个 NULL 当作相同值归为一组;SQLite 默认不这样,会把每个 NULL 视为不同。
如果你发现某组数据“莫名消失”或“意外合并”,先查:
SELECT student_id, course_id, COUNT(*) FROM enrollment GROUP BY student_id, course_id ORDER BY student_id NULLS FIRST, course_id NULLS FIRST;
排查建议:
- 用
COUNT(*) FILTER (WHERE student_id IS NULL AND course_id IS NULL)(PostgreSQL)或SUM(CASE WHEN student_id IS NULL AND course_id IS NULL THEN 1 ELSE 0 END)统计双 NULL 组数量 - 若业务上
NULL表示“未知”,建议改用占位符如'UNKNOWN'避免歧义 - MySQL 5.7+ 若开启
sql_mode=ONLY_FULL_GROUP_BY,会严格报错,反而帮你提前暴露问题
性能关键:复合索引必须匹配 GROUP BY 顺序
GROUP BY student_id, course_id 要快,得靠索引,但不是随便建个复合索引就行。索引字段顺序必须和 GROUP BY 列顺序一致,且不能中间跳过——否则只能用上前缀部分。
例如:
- ✅ 有效索引:
CREATE INDEX idx_enroll_sc ON enrollment (student_id, course_id); - ❌ 无效索引:
CREATE INDEX idx_enroll_cs ON enrollment (course_id, student_id);(顺序反了,无法加速GROUP BY student_id, course_id) - ⚠️ 半有效索引:
CREATE INDEX idx_enroll_scd ON enrollment (student_id, course_id, status);(前两列匹配,第三列不影响分组,但可用于HAVING或后续ORDER BY)
执行计划里看到 Using filesort 或 Using temporary 就说明没走索引分组,得检查索引定义。
别把多列分组和多列匹配(WHERE 中的 (a,b) = (...))混用
这是两个完全不同的语法场景,但名字像、写法像,新手常套错。分组是 GROUP BY a, b,匹配是 WHERE (a, b) = ('x', 'y')。后者是 MySQL/PostgreSQL 支持的语法糖,本质是 a = 'x' AND b = 'y' 的简写,跟分组无关。
典型误用:
-- ❌ 错误:试图用多列匹配语法替代分组 SELECT (student_id, course_id), COUNT(*) FROM enrollment GROUP BY (student_id, course_id); <p>-- ✅ 正确:分组就是逗号分隔,括号纯属干扰 SELECT student_id, course_id, COUNT(*) FROM enrollment GROUP BY student_id, course_id;</p>
注意:(student_id, course_id) 在 SELECT 中是行构造器,返回一个匿名行类型,在大多数场景下没法直接参与聚合或排序,也不利于下游应用解析。











