子查询不能直接实现动态行转列,必须用CASE WHEN+聚合函数做静态转换,或用动态SQL拼接执行;标量子查询仅支持单值,多行会报“Subquery returns more than 1 row”错误。

子查询不能直接做动态行转列,得用条件聚合或动态SQL
SQL 标准语法里,SELECT 子句中的子查询必须返回单值(标量子查询),否则会报错 Subquery returns more than 1 row。想把“多行数据按类别展开成多列”,比如把不同 product_type 的销售额变成 type_A_sales、type_B_sales 等列,靠普通子查询硬写是走不通的。
真正可行的路径只有两条:
- 静态场景:用
CASE WHEN+SUM做条件聚合(推荐优先尝试) - 动态场景:拼接 SQL 字符串再用
EXECUTE IMMEDIATE(Oracle)、sp_executesql(SQL Server)或PREPARE/EXECUTE(PostgreSQL)执行
用 CASE WHEN 实现静态行转列,避免子查询嵌套
这是最常用、最稳定的方式。核心是把“每个类别”转化为一个带条件的聚合表达式,所有列在同一个 GROUP BY 中计算。
假设表 sales 有字段 region、product_type、amount,想按地区汇总各类型销售额:
SELECT region, SUM(CASE WHEN product_type = 'A' THEN amount ELSE 0 END) AS sales_A, SUM(CASE WHEN product_type = 'B' THEN amount ELSE 0 END) AS sales_B, SUM(CASE WHEN product_type = 'C' THEN amount ELSE 0 END) AS sales_C FROM sales GROUP BY region;
注意点:
-
CASE必须配合聚合函数(如SUM、COUNT),否则会因未GROUP BYproduct_type报错 - 别用
ELSE NULL——NULL 会让整列聚合结果变 NULL;统一用ELSE 0 - 列名要手动写死,新增类型就得改 SQL,不适合类别数不固定的情况
MySQL/PostgreSQL 动态行转列必须拼字符串 + 执行
当 product_type 值来自业务表、随时增减时,硬编码 CASE 不现实。这时得先查出所有类型,拼成 SQL 字符串,再执行。
以 PostgreSQL 为例:
-- 第一步:生成列定义片段(实际要用 DO 块或应用层拼)
SELECT string_agg(
format('SUM(CASE WHEN product_type = %L THEN amount ELSE 0 END) AS sales_%s',
product_type, lower(product_type)),
', '
) FROM (SELECT DISTINCT product_type FROM sales) t;结果类似:SUM(CASE WHEN product_type = 'A' THEN amount ELSE 0 END) AS sales_a, SUM(...) AS sales_b...
然后把这个字符串插进完整 SQL 里,用 EXECUTE 运行。MySQL 类似,但要用 CONCAT + PREPARE/EXECUTE。
关键风险点:
- 拼接前必须对
product_type做白名单校验或转义,否则有 SQL 注入风险 - PostgreSQL 中
EXECUTE无法直接返回结果集,通常得写成函数并用RETURN QUERY EXECUTE - MySQL 的
PREPARE语句不能参数化列名,只能拼字符串,且作用域限于当前会话
为什么不该在 SELECT 里塞相关子查询来“模拟”行转列
有人会写这种结构:
SELECT DISTINCT region, (SELECT SUM(amount) FROM sales s2 WHERE s2.region = s1.region AND s2.product_type = 'A') AS sales_A, (SELECT SUM(amount) FROM sales s2 WHERE s2.region = s1.region AND s2.product_type = 'B') AS sales_B FROM sales s1;
表面能跑,但问题明显:
- 每列都触发一次全表扫描(或索引查找),N 列 = N 次独立查询,性能随列数线性恶化
- 如果
region有 1000 行,3 列就等于执行 3000 次子查询 - 无法利用
GROUP BY的哈希聚合优化,内存和 CPU 开销远高于CASE WHEN单次扫描 - 一旦子查询返回多行(比如漏了
SUM),立刻报错Subquery returns more than 1 row
动态性需求越强,越要警惕这种“看起来简单”的写法——它掩盖了执行计划失控的风险。
真正难的不是写出能跑的 SQL,而是让数据库只扫一遍数据就把所有列算出来。动态列名这件事,数据库本身不支持,就得靠程序逻辑兜底,或者接受维护成本换灵活性。

















