必须用CROSS APPLY调用表值函数当且仅当右侧逻辑依赖左侧当前行字段且返回多行或多列结构化数据,如STRING_SPLIT、OPENJSON或自定义表值函数。

什么时候必须用 CROSS APPLY 调用表值函数
只有当右侧逻辑**依赖左侧当前行的字段值**,且返回结果是**多行或多列结构化数据**时,CROSS APPLY才不可替代。普通标量子查询(比如 (SELECT MAX(x) FROM t2 WHERE t2.id = t1.id))完全没必要上 CROSS APPLY,反而增加理解成本和执行开销。
常见刚性场景包括:
-
STRING_SPLIT(p.Tags, ','):每行的逗号分隔字符串需独立拆成多行 -
OPENJSON(c.ProductList):每行 JSON 字段需单独解析,且需WITH显式定义列类型 - 自定义表值函数如
dbo.GetRelatedProducts(p.Price):函数内部基于传入参数做复杂过滤或计算,返回一个结果集
CROSS APPLY 调用函数时的括号与别名不能省
语法上,右侧必须是带括号的表达式,且必须指定别名。写成 CROSS APPLY dbo.F_TConversion(a.num) 会报错;正确写法是:
CROSS APPLY dbo.F_TConversion(a.num) AS conv
漏掉括号或别名会导致 SQL Server 解析失败,错误信息类似:Incorrect syntax near 'AS' 或 A table alias is required。别名不是可选装饰,而是后续在 SELECT 或 WHERE 中引用右侧列的唯一途径。
性能陷阱:没索引 + 没 TOP 的子查询会拖垮查询
如果右侧是子查询而非函数,又没加 TOP 和有效 ORDER BY,CROSS APPLY 会退化为对左侧每一行都执行一次全表扫描。例如:
CROSS APPLY (SELECT * FROM logs l2 WHERE l2.order_id = orders.id) AS latest_log
这比等价的 INNER JOIN 更慢——因为优化器无法启用哈希连接,只能走嵌套循环,且每次都要扫全表 logs。
实际应改成:
CROSS APPLY (SELECT TOP 1 * FROM logs l2 WHERE l2.order_id = orders.id ORDER BY l2.created_at DESC) AS latest_log
并确保有复合索引:IX_logs_order_id_created_at ON logs (order_id, created_at DESC)。否则即使加了 TOP 1,也起不到加速效果。
OUTER APPLY 才能保留左表空关联行
CROSS APPLY 天然丢弃右侧无结果的左表行,行为等同于 INNER JOIN。若需要“不管有没有匹配结果,左表每行都出现”,必须换用 OUTER APPLY。
典型例子:查每个用户最新订单,但有些用户从未下单——用 CROSS APPLY 会直接剔除这些用户;用 OUTER APPLY 则保留用户信息,订单字段为 NULL。
注意:OUTER APPLY 的右侧子查询里仍可安全使用 TOP 和 ORDER BY,它只影响“是否保留空结果”,不改变右侧自身的执行逻辑。
真正难处理的,是右侧函数或子查询内部存在非确定性逻辑(比如依赖 GETDATE() 且未控制精度)、或跨库/跨服务器调用导致执行计划不可预测。这种情况下,CROSS APPLY 的“逐行调用”特性会放大延迟和不一致性,得先解决底层稳定性问题,再谈语法优化。

















