因mysql无原生pivot,max+case when是兼容性最强的行转列方案:通过group by分组后,用case筛选目标值、max作为占位聚合器压平多行,确保每组每列仅一个有效值。

为什么用 MAX 配合 CASE WHEN 做行转列
因为 MySQL 没有原生的 PIVOT 语法,MAX(CASE WHEN ...) 是最稳定、兼容性最好(5.7+ 全支持)、且能处理 NULL 值的通用方案。它本质是利用聚合函数“压平”多行数据:对每组分组键(如 user_id),把符合条件的单个值挑出来,其他行对应位置填 NULL,再用 MAX 忽略 NULL 取到那个有效值。
注意:MIN、MAX 效果一致(只要每组最多一个非 NULL 值),但别用 SUM 或 COUNT——除非你真想累加或计数。
CASE WHEN 中漏写 ELSE NULL 会出什么问题
不写 ELSE 默认返回 NULL,看似安全,但一旦字段本身允许 NULL,就无法区分“该行本不该匹配”和“该行匹配了但值就是 NULL”。结果是 MAX 仍返回 NULL,看起来像没数据,实则逻辑被掩盖。
务必显式写出 ELSE NULL,并确保业务上 NULL 有明确定义:
MAX(CASE WHEN subject = '数学' THEN score ELSE NULL END) AS math_score
- 如果
score字段本身为 NULL,这条记录在math_score列中就是 NULL - 如果某用户根本没有“数学”记录,
CASE全部不命中,也返回 NULL —— 两者表现一致,但至少你知道这是设计使然 - 若想把“无记录”显示为 0,可写
ELSE 0,但要确认这符合业务语义
GROUP BY 的字段必须覆盖所有非聚合列
行转列本质是分组聚合,SELECT 中所有未被聚合函数包裹的字段,都必须出现在 GROUP BY 中。常见错误是只写主键,却忘了关联表里的其他展示字段。
例如从 students 和 scores 表联查,想按学生信息转科目的成绩:
SELECT s.name, s.grade, MAX(CASE WHEN sc.subject='语文' THEN sc.score END) AS chinese, MAX(CASE WHEN sc.subject='数学' THEN sc.score END) AS math FROM students s JOIN scores sc ON s.id = sc.student_id GROUP BY s.id, s.name, s.grade -- 必须包含所有 SELECT 中的非聚合字段
- 只写
GROUP BY s.id在 MySQL 5.7 严格模式下会报错:Expression #2 of SELECT list is not in GROUP BY clause - MySQL 8.0+ 默认开启
ONLY_FULL_GROUP_BY,这个限制更刚性 - 别依赖
sql_mode关闭它——掩盖的是逻辑缺陷,不是问题本身
性能瓶颈常出在缺失复合索引上
行转列查询通常涉及大表 JOIN + GROUP BY,慢就慢在分组前没过滤好。关键索引不是单列 subject,而是以分组字段为前导的复合索引。
假设按 student_id 分组,查各科 score:
- ❌ 错误索引:
INDEX(subject)—— 对分组无加速作用 - ✅ 推荐索引:
INDEX(student_id, subject, score)—— 覆盖 JOIN 条件、分组依据、以及 SELECT 中用到的值,极大减少回表 - 如果
scores表还有时间字段用于范围筛选(如最近一年),可扩展为:INDEX(student_id, created_at, subject, score)
执行前用 EXPLAIN 看 type 是否为 ref 或 range,key 是否命中你的复合索引——这才是真正起效的信号。











