必须先确认数据库类型,因pivot仅sql server和oracle原生支持,postgresql、mysql、sqlite不支持;跨库行转列推荐case when+group by,动态列需应用层拼接sql。

用 PIVOT 做行转列前必须先确认数据库类型
PIVOT 不是所有 SQL 引擎都支持。SQL Server 和 Oracle 支持原生 PIVOT 语法,PostgreSQL、MySQL(8.0.1+)、SQLite 则不支持——强行写会直接报错 ERROR: syntax error at or near "PIVOT" 或类似提示。
如果你在 pgAdmin 里粘贴了带 PIVOT 的语句却没报错,大概率是误用了扩展(比如 tablefunc 的 crosstab()),那已不是标准 SQL 行为。
- SQL Server:可用
PIVOT,但要求聚合函数(如MAX、SUM)包裹值列,且列名必须明确写出 - MySQL:只能用
CASE WHEN+GROUP BY模拟,否则得靠应用层或临时表中转 - PostgreSQL:推荐用
CROSSTAB()(需启用tablefunc扩展),或手写CASE WHEN
用 CASE WHEN 实现兼容性最强的行转列
这是跨数据库最稳妥的方式,核心逻辑是:对每个目标列,用 CASE WHEN 判断原始行是否属于该列,匹配则取值,否则置为 NULL,再配合 GROUP BY 和聚合函数收拢成一行。
假设源表 sales 结构为:region(地区)、product(品类)、amount(销售额),想按 region 分组,把 product 值(如 'A'、'B'、'C')转为列:
SELECT region, SUM(CASE WHEN product = 'A' THEN amount ELSE 0 END) AS A, SUM(CASE WHEN product = 'B' THEN amount ELSE 0 END) AS B, SUM(CASE WHEN product = 'C' THEN amount ELSE 0 END) AS C FROM sales GROUP BY region;
- 注意:用
SUM而非MAX取决于业务含义——如果一个region+product组合有多条记录,SUM是累加,MAX是取最大值 - 别漏掉
ELSE 0(或ELSE NULL),否则空分支会导致整列结果为NULL - 若
product值动态不定(比如来自用户输入),CASE WHEN就无法硬编码,得用动态 SQL 拼接,这时就得切到应用层处理
插入新表时,CREATE TABLE AS 比 INSERT INTO ... SELECT 更省事
如果目标是生成一张全新的宽表(比如叫 sales_pivot),优先用 CREATE TABLE AS(PostgreSQL/MySQL 8.0+/SQL Server 支持),它能自动建表并写入数据,一步到位:
CREATE TABLE sales_pivot AS SELECT region, SUM(CASE WHEN product = 'A' THEN amount ELSE 0 END) AS A, SUM(CASE WHEN product = 'B' THEN amount ELSE 0 END) AS B, SUM(CASE WHEN product = 'C' THEN amount ELSE 0 END) AS C FROM sales GROUP BY region;
- 如果目标表已存在,且字段类型/顺序不完全匹配,就得用
INSERT INTO target_table (...) SELECT ...,并确保列数、类型、顺序严格一致 - SQL Server 中若用
SELECT INTO(等价于CREATE TABLE AS),目标表不能预先存在,否则报错There is already an object named 'xxx' in the database - 某些场景下需要保留原始
NULL而非填0,那就把ELSE 0改成ELSE NULL,但要注意后续计算是否允许空值
动态列场景下,PIVOT 和 CASE WHEN 都无解
当要转成列的值(如 product 名称)不固定、来自运行时数据时,无论 PIVOT 还是静态 CASE WHEN 都无法直接处理——它们都需要编译期就确定列名。
此时唯一可行路径是:先查出所有待转列名(如 SELECT DISTINCT product FROM sales),在应用代码里拼出完整 SQL,再执行。例如 Python 中:
cols = ["'{}'".format(p) for p in products] # ['A', 'B', 'C']
sql = f"SELECT region, " + \
", ".join([f"SUM(CASE WHEN product = {c} THEN amount ELSE 0 END) AS {c}" for c in cols]) + \
" FROM sales GROUP BY region"
- 手动拼 SQL 有注入风险,务必对列名做白名单校验或双下划线转义(如只允许字母数字)
- 列数过多(如几百个)会导致 SQL 过长、解析慢,甚至触发 MySQL 的
max_allowed_packet限制 - 有些 BI 工具(如 Metabase、Superset)内置了“透视”功能,本质就是帮你做了这步动态拼接,比手写更安全











