CROSS APPLY 是解决右端依赖左端字段且须逐行动态计算的唯一合规手段,适用于表值函数、TOP/ORDER BY子查询、OPENJSON/STRING_SPLIT等场景,普通JOIN因无法引用左表列而报错。

CROSS APPLY 不是用来“替代 JOIN”的语法糖,而是解决「右端依赖左端字段且必须逐行动态计算」这类问题的唯一合规手段。它不是可选项,是必要项——当你右边是个函数、带 TOP/ORDER BY 的子查询、或 OPENJSON/STRING_SPLIT 这类行集生成器时,CROSS APPLY 就是 SQL Server 给你开的那扇门。
什么时候非用 CROSS APPLY 不可?
普通 INNER JOIN 要求右表是静态关系,而以下场景右端根本“静不下来”:
- 右侧是表值函数(如
dbo.SplitTags(u.Tags)),且参数来自左表列u.Tags - 右侧子查询含
TOP 3 ORDER BY CreatedDate DESC,且WHERE条件里引用了左表的u.UserId - 右侧是
OPENJSON(u.ConfigJson)或STRING_SPLIT(u.Roles, ','),输入值随左表每行变化 - 右侧是
VALUES构造(如动态生成多条状态记录),且需用左表字段填充值
这些写法在 FROM ... JOIN 中直接报错:The multi-part identifier "u.Tags" could not be bound. —— 因为 JOIN 的 ON 子句不接受表达式中对右端的列引用,而 APPLY 允许。
CROSS APPLY 右侧写子查询的坑与要点
最常见误用:把 CROSS APPLY 当成“高级 WHERE”,结果性能崩盘。关键不在 APPLY,而在右侧子查询是否可优化:
- 必须显式写出关联条件(如
WHERE o.UserId = u.Id),否则变成笛卡尔积 -
TOP N+ORDER BY必须配覆盖索引,例如(UserId, CreatedDate DESC) INCLUDE (Amount),否则每行都触发一次全索引扫描 - 避免在右侧子查询中用标量函数(如
dbo.CalcScore(o.Amount)),它会在左侧每行重复执行;应提前算好或改用计算列 - 如果右侧子查询返回空,整行被丢弃(类似
INNER JOIN);要保留左表行,请换用OUTER APPLY
示例(查每个用户最新一笔订单):
SELECT u.Name, o.OrderId, o.OrderDate
FROM Users u
CROSS APPLY (
SELECT TOP 1 OrderId, OrderDate
FROM Orders o2
WHERE o2.UserId = u.Id -- 关键:关联字段必须出现在 WHERE 中
ORDER BY o2.OrderDate DESC
) o;右侧用表值函数时,性能陷阱比语法更致命
90% 的慢 CROSS APPLY 查询,根子在右侧函数本身。判断它是不是“坏函数”,看三点:
- 执行计划里右侧操作符旁有黄色感叹号(缺失索引)或红色叉号(隐式转换)
- 单独执行该函数(如
SELECT * FROM dbo.GetOrdersByUser(123))耗时明显,或执行计划显示Clustered Index Scan - 函数定义含
BEGIN ... END、INSERT INTO @t、循环或临时表 → 这是多语句 TVF,无法内联,优化器只能估 1 行,计划严重失真
解法只有一条:重写为内联 TVF(RETURNS TABLE AS RETURN (SELECT ...))。它会被优化器展开,能走索引、能下推谓词、能参与整体计划优化。别试图“调优 APPLY”,要调的是右侧那个函数。
CROSS APPLY 和 OUTER APPLY 切换的逻辑边界
二者行为差异只在“右端无结果时是否丢弃左行”,但这个选择直接影响业务语义:
- 用
CROSS APPLY:只要右端没产出,就代表“该用户无有效数据”,比如“查每个用户最近一笔已支付订单”——没支付的订单不该出现 - 用
OUTER APPLY:右端为空也要展示左表信息,比如“查每个用户最新订单(含未支付)”,或“查用户+其权限列表”,没权限也要显示用户名 - 不能靠加
WHERE o.OrderId IS NOT NULL把OUTER APPLY“伪造成”CROSS APPLY,这会阻止优化器下推,反而更慢
真正容易被忽略的点是:当右侧是复杂子查询(尤其含 TOP+ORDER BY)时,OUTER APPLY 的执行计划可能比 CROSS APPLY 多出一倍逻辑读——因为 SQL Server 仍要为每行尝试执行右侧,哪怕最终只填 NULL。这时候得权衡:是接受稍慢但语义完整,还是先过滤左表再用 CROSS APPLY?

















