MySQL行转列须用CASE WHEN+GROUP BY+聚合函数;SQL Server用PIVOT但语法易错;PostgreSQL需tablefunc扩展的crosstab();三者均需注意索引与性能优化。

MySQL里没有PIVOT,得用CASE WHEN手动聚合
MySQL 8.0 之前压根不支持 PIVOT,官方至今也没加。想行转列,唯一靠谱的路就是靠 CASE WHEN + GROUP BY + 聚合函数组合硬写。
常见错误是漏掉 GROUP BY,结果每组只出一行、数据全塌缩;或者 CASE WHEN 没配对 ELSE 0(或 NULL),导致某列值缺失时整行变 NULL。
- 必须对原始表中用于分组的字段(比如
user_id、date)做GROUP BY - 每个目标列对应一个
MAX(CASE WHEN ... THEN value END),别用SUM除非真要累加 - 如果源数据有重复
(user_id, category)组合,先确认业务逻辑:是取最新?最大?还是该去重?否则聚合结果会偏高
SELECT user_id, MAX(CASE WHEN category = 'A' THEN score END) AS score_A, MAX(CASE WHEN category = 'B' THEN score END) AS score_B FROM scores GROUP BY user_id;
SQL Server用PIVOT更简洁,但语法容易写反
PIVOT 看似省事,实际踩坑集中在三处:旋转字段写错位置、聚合函数强制要求、以及 IN 子句必须是确定值——不能套子查询或变量。
典型报错是 "Incorrect syntax near 'PIVOT'",大概率因为没把待旋转的列名(如 category)放在 FOR 后面,而把值列(如 score)错放进去;或者漏写了最外层的 SELECT * 包裹。
-
PIVOT只接受一个聚合函数,MAX/SUM/COUNT都行,但不能省略 -
IN (N'A', N'B')里的值必须显式写出,且类型要和原字段一致(中文注意加N'') - 原表若含多条相同
(user_id, category)记录,PIVOT会自动按聚合函数合并,无需预处理——这点比CASE WHEN更省心
SELECT user_id, A AS score_A, B AS score_B FROM (SELECT user_id, category, score FROM scores) src PIVOT (MAX(score) FOR category IN ([A], [B])) p;
PostgreSQL用crosstab()函数,得先装扩展
PostgreSQL 原生不带 PIVOT,得靠 tablefunc 扩展里的 crosstab()。不装扩展直接调用会报错:function crosstab(unknown) does not exist。
它比前两种更“函数化”,但也更难调试:参数顺序固定、返回列定义必须严格匹配、空值默认不补 NULL 而是跳过整行——容易莫名丢数据。
- 首次使用前必须执行:
CREATE EXTENSION IF NOT EXISTS tablefunc; - 第一个参数是 SQL 查询字符串(需按
rowid, category, value三列排序),第二个参数可选,用于指定列名列表 - 调用
crosstab()的地方必须紧跟AS子句,明确定义返回字段名和类型,比如AS t(user_id TEXT, A NUMERIC, B NUMERIC)
SELECT * FROM crosstab(
'SELECT user_id, category, score FROM scores ORDER BY 1,2',
'VALUES (''A''), (''B'')'
) AS t(user_id TEXT, score_A NUMERIC, score_B NUMERIC);
行转列后性能掉得厉害?先看执行计划再动手
无论哪种写法,行转列本质是 GROUP BY + 多次扫描,数据量一过百万,响应就明显变慢。不是语法问题,是模型硬伤。
最容易被忽略的是索引失效:比如 CASE WHEN category = 'A' 这种条件,即使 category 有索引,优化器也可能放弃走索引而全表扫——尤其当 category 值分布极不均匀时。
- 在
GROUP BY字段和CASE WHEN判断字段上建联合索引,例如(user_id, category) - SQL Server 的
PIVOT如果内层子查询没加ORDER BY,可能导致结果错位,但优化器通常不报错,得人工核对 - 如果只是前端展示需要行转列,优先考虑在应用层(Python/JS)处理,数据库只返回明细,避免把聚合压力全压给 DB
复杂点从来不在语法本身,而在你有没有看过 EXPLAIN 或执行计划里那几行红色警告。










