cross apply 用于逐行动态关联,右侧可引用左侧当前行字段;lag/lead 才用于跨行取值,因其具备行序感知与偏移能力,而 cross apply 无此功能。

直接说结论:CROSS APPLY 不是用来“跨行关联”的,它是用来做「逐行动态关联」的——右侧子查询或表值函数能引用左侧当前行的字段,而普通子查询做不到。如果你真想按顺序把前一行、后一行的值拉进来(比如计算环比、差值),该用的是窗口函数 LAG() 或 LEAD(),不是 APPLY。
为什么 CROSS APPLY 不能替代 LAG/LEAD?
CROSS APPLY 的本质是“对左侧每一行,执行一次右侧查询”,它不感知行序、不维护上下文,也不提供“上一行”“下一行”这样的偏移能力。你写 SELECT *, (SELECT TOP 1 value FROM t2 WHERE t2.id ,这看着像取上一行,但实际是靠子查询手动找,性能差、逻辑绕,还容易出错。
真正跨行取值,必须依赖窗口函数:
-
LAG(value, 1):取当前行向上第 1 行的value -
LEAD(value, 2):取当前行向下第 2 行的value - 必须配合
ORDER BY子句(在OVER中),否则结果无序、不可靠
CROSS APPLY 真正适合做什么?
它擅长处理“每行需要独立计算一组结果”的场景,尤其是右侧依赖左侧字段、且结果是多行或结构化数据时。常见于:
- 拆分字符串字段:如
Tags字段存'A,B,C',用表值函数STRING_SPLIT()配合CROSS APPLY展开成多行 - 取每个用户的最新 N 条订单:
OUTER APPLY (SELECT TOP 3 * FROM Orders o WHERE o.UserId = u.Id ORDER BY OrderDate DESC) - 调用自定义表值函数(TVF):函数内部可基于传入的
@id做复杂逻辑,返回一个结果集
注意:CROSS APPLY 是 SQL Server 特有语法,MySQL / PostgreSQL 不支持;PostgreSQL 对应的是 LATERAL 子查询。
常见错误:把 APPLY 当成 JOIN 用
有人写 SELECT * FROM t1 CROSS APPLY (SELECT * FROM t2 WHERE t2.x = t1.x) AS t2a,以为这和 INNER JOIN 一样。其实二者执行计划可能完全不同:
-
INNER JOIN可能走哈希或合并连接,一次性匹配全量 -
CROSS APPLY默认倾向嵌套循环,对左侧每行都查一遍右侧,当左侧数据量大、右侧无有效索引时,性能会断崖式下跌 - 如果右侧子查询没加
WHERE或TOP限制,又没索引支撑,很容易拖垮整个查询
什么时候该换回子查询或 CTE?
如果你发现 CROSS APPLY 写出来很别扭,比如:
- 右侧只是简单取一个标量值(如
(SELECT MAX(x) FROM t2 WHERE t2.id = t1.id)),直接用相关子查询更清晰 - 需要多次复用右侧结果(比如既用于计算又用于过滤),
CROSS APPLY无法在同一个SELECT中重用别名,得改用WITHCTE 先物化 - 目标数据库要兼容 PostgreSQL 或 MySQL,那就不能用
APPLY,得提前规划替代方案
最易被忽略的一点:SQL Server 2012+ 才完整支持 STRING_SPLIT() 这类内建 TVF;老版本得自己写函数,而且记得加 RETURNS TABLE 和 WITH SCHEMABINDING,否则 CROSS APPLY 可能无法正确推导统计信息,影响执行计划。










