SUM(CASE WHEN...)是最通用的行转列写法,因所有主流数据库均支持CASE WHEN与聚合函数,而PIVOT仅SQL Server/Oracle支持,MySQL不支持;必须搭配GROUP BY分组和ELSE 0避免NULL干扰。

为什么 SUM(CASE WHEN ...) 是最通用的行转列写法
因为几乎所有关系型数据库(MySQL、PostgreSQL、SQL Server、Oracle)都支持 CASE WHEN 和聚合函数,而 PIVOT 仅 SQL Server 和 Oracle 原生支持,MySQL 甚至没有 PIVOT 关键字。用 SUM(CASE WHEN ...) 能避免跨库迁移时重写逻辑。
常见错误是漏写 GROUP BY —— 行转列本质是先分组再对每组做条件聚合,没 GROUP BY 就会把全表压成一行:
SELECT dept, SUM(CASE WHEN month = 'Jan' THEN sales ELSE 0 END) AS jan_sales, SUM(CASE WHEN month = 'Feb' THEN sales ELSE 0 END) AS feb_sales FROM sales_data GROUP BY dept; -- 必须有!否则所有部门数据被合并
注意:用 ELSE 0 而不是 ELSE NULL,否则 SUM 遇到全 NULL 会返回 NULL,而不是 0。
SQL Server 里 PIVOT 的三个硬性前提
PIVOT 不是语法糖,它对输入结构有强约束,不满足就直接报错:The PIVOT operator requires a value for the aggregate function 或 Incorrect syntax near 'PIVOT'。
- 必须显式指定一个聚合函数(
SUM、COUNT、MAX等),不能省略 - 旋转列(即
IN子句里的值)必须是确定的、硬编码的字面量,不能是子查询或变量 - 原始数据中,
GROUP BY字段 + 旋转字段 + 聚合字段三者必须能构成完整键;如果同一组内存在重复的旋转值(如 dept=A, month='Jan' 出现两次),PIVOT会自动聚合,但结果可能不符合预期
示例(正确写法):
SELECT dept, [Jan], [Feb], [Mar] FROM ( SELECT dept, month, sales FROM sales_data ) AS src PIVOT ( SUM(sales) FOR month IN ([Jan], [Feb], [Mar]) ) AS pvt;
MySQL 没有 PIVOT,但可以用 IF 替代 CASE WHEN
MySQL 支持 IF(condition, true_value, false_value),语义和 CASE WHEN 等价,写起来更短。但要注意:所有分支必须返回相同类型,否则隐式转换可能导致数值截断或字符串拼接异常。
比如 sales 是 DECIMAL(10,2),但写成 IF(month='Jan', sales, '') 就会让整列变成字符串,SUM 再计算就出错。
安全写法是统一用 0 占位:
SELECT dept, SUM(IF(month = 'Jan', sales, 0)) AS jan_sales, SUM(IF(month = 'Feb', sales, 0)) AS feb_sales FROM sales_data GROUP BY dept;
另外,MySQL 8.0+ 支持窗口函数,但行转列仍不支持动态列名——列名必须在 SQL 文本里写死,没法像应用层那样循环生成。
动态行转列只能靠应用层拼 SQL,数据库做不到
当月份数量不固定(比如要展示最近 12 个月),或者产品种类随业务增长而增加,数据库本身无法“自动发现”有哪些值要转成列。PIVOT 的 IN 列表、CASE WHEN 的分支都得提前知道值列表。
可行路径只有一条:先查出所有要转的值(如 SELECT DISTINCT month FROM sales_data ORDER BY month LIMIT 12),在 Python/Java/Node.js 里拼出完整 SQL,再执行。
容易被忽略的是 SQL 注入风险——如果这些值来自用户输入(比如前端选了 “Q1”, “Q2”),必须严格校验格式(如只允许字母、数字、短横线),不能直接拼接。










