cross apply 本身不用于行转列,它只是把右表表达式按左表每行“重执行一次”,真正实现行转列得靠 pivot 或聚合 + case when;但子查询配合 cross apply 可以优雅地生成中间结构,为后续转列铺路——尤其当列名动态、来源非固定字段时。

直接说结论:CROSS APPLY 本身不用于行转列,它只是把右表表达式按左表每行“重执行一次”,真正实现行转列得靠 PIVOT 或聚合 + CASE WHEN;但子查询配合 CROSS APPLY 可以优雅地生成中间结构,为后续转列铺路——尤其当列名动态、来源非固定字段时。
为什么不能直接用 CROSS APPLY 做行转列
CROSS APPLY 是横向展开,不是列结构变换。它常被误用,比如写成:
SELECT t.id, ca.val FROM orders t CROSS APPLY (SELECT item_name FROM order_items WHERE order_id = t.id) ca
这只会把多行数据“堆成一列”,结果仍是多行,不是把多个 item_name 拆成 item1、item2 这样的列。
- 错误现象:
CROSS APPLY后仍返回 N 行/订单,而非 1 行/订单 + 多列 - 本质:它是“行级 JOIN”,不是“结构重塑”
- 真正需要的:先用
CROSS APPLY构造带序号或分类键的中间结果,再交给PIVOT或聚合处理
子查询 + CROSS APPLY 生成可 Pivot 的中间结构
典型场景:一个订单有多个商品,想转成 product_1、product_2、product_3 列。关键在于给每个商品分配位置序号。
SELECT t.order_id, ca.seq, ca.product_name
FROM orders t
CROSS APPLY (
SELECT
ROW_NUMBER() OVER (ORDER BY item_id) AS seq,
product_name
FROM order_items oi
WHERE oi.order_id = t.order_id
) ca
这样输出就是三列:order_id、seq(1/2/3…)、product_name,满足 PIVOT 输入要求。
-
ROW_NUMBER()必须加OVER,且排序依据要明确(不能依赖无序的物理存储) - 子查询里不能引用外部列以外的变量,否则会报
Invalid column name - 若某订单只有 1 个商品,
seq=1之外的列在PIVOT后自动为NULL,无需额外补空
接 PIVOT 完成最终行转列
把上一步结果当内层查询,再套 PIVOT:
SELECT order_id, [1] AS product_1, [2] AS product_2, [3] AS product_3
FROM (
SELECT t.order_id, ca.seq, ca.product_name
FROM orders t
CROSS APPLY (
SELECT ROW_NUMBER() OVER (ORDER BY item_id) AS seq, product_name
FROM order_items oi
WHERE oi.order_id = t.order_id
) ca
) src
PIVOT (MAX(product_name) FOR seq IN ([1],[2],[3])) p
注意点:
-
PIVOT聚合函数必须写(哪怕只有一值),常用MAX()或MIN(),不能省略 -
FOR seq IN ([1],[2],[3])中的数值必须是确定字面量,无法直接动态;如需动态列,得拼 SQL 字符串 +EXEC - 如果
seq超过 [1]-[3],对应值会被丢弃;建议先查最大seq再决定IN列表
替代方案:不用 PIVOT,用 GROUP BY + CASE
更可控、兼容性更好(支持 SQL Server 2005+),且避免动态列限制:
SELECT order_id, MAX(CASE WHEN ca.seq = 1 THEN ca.product_name END) AS product_1, MAX(CASE WHEN ca.seq = 2 THEN ca.product_name END) AS product_2, MAX(CASE WHEN ca.seq = 3 THEN ca.product_name END) AS product_3 FROM orders t CROSS APPLY ( SELECT ROW_NUMBER() OVER (ORDER BY item_id) AS seq, product_name FROM order_items oi WHERE oi.order_id = t.order_id ) ca GROUP BY order_id
这个写法实际更常用,原因很实在:
- 不需要记忆
PIVOT语法顺序(FOR和IN容易写反) - 聚合逻辑一目了然,调试时可单独查
ca.seq分布 - 加新列只需复制一行
CASE,不用改PIVOT整体结构 - 性能差异极小,执行计划通常一致
真正容易被忽略的是 ROW_NUMBER() 的排序依据——如果没指定明确字段(比如用 ORDER BY (SELECT NULL)),SQL Server 可能每次返回不同序号,导致列错位。别图省事跳过这步。











