核心区别是右表表达式能否引用左表字段:join不能,apply能;cross apply逐行执行右侧逻辑并仅保留有结果的左行,outer apply则保留无匹配的左行并以null填充右列。

CROSS APPLY / OUTER APPLY 和 JOIN 的核心区别,就一条:右表表达式能不能引用左表字段。不能的话,硬用 JOIN 也能绕出来,但写法臃肿、性能差、逻辑难维护。
APPLY 能让右表“看到”左表字段,JOIN 不能
这是最根本的差异。JOIN 的 ON 子句只能做等值或条件匹配,左右表是“平行关系”;而 APPLY 的右侧子查询或表值函数可以自由使用左侧表的列,比如 WHERE o.customer_id = c.id 中的 c.id 就来自左表 customer c。
-
CROSS APPLY相当于“对左表每一行,执行一次右表子查询”,只保留右表有结果的行 -
OUTER APPLY同样逐行执行右表子查询,但即使右表没结果,左表该行也保留,右表字段填NULL -
INNER JOIN是先做笛卡尔积再过滤,ON里不能出现“依赖左表计算出的右表范围”——比如“取每个用户的最新 3 条订单”,这个“最新 3 条”必须基于customer_id动态算,JOIN 做不到原生支持
什么时候非用 APPLY 不可
典型场景是“一对多中取 Top N”或“调用表值函数”,这时 JOIN 很难干净实现:
- 查每个客户最近 2 笔订单:
OUTER APPLY (SELECT TOP 2 * FROM orders WHERE customer_id = c.id ORDER BY order_date DESC)——c.id在子查询里直接可用 - 用表值函数展开地址信息:
CROSS APPLY dbo.ParseAddress(c.raw_address),函数参数必须传左表字段 - LEFT JOIN 写同样逻辑要套
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC)+ 外层过滤,语句变长、执行计划更重、索引利用率可能下降
性能和执行顺序的隐含代价
APPLY 表面简洁,但底层是“嵌套循环”:左表每行都触发一次右表子查询。这意味着:
- 如果左表有 10 万行,右表子查询哪怕加了索引,也可能执行 10 万次——比 JOIN 的哈希连接或合并连接开销大得多
- 右表子查询不能提前优化;SQL Server 不会把它“提升”成全局物化结果,每次都是独立执行
-
OUTER APPLY比LEFT JOIN更容易触发警告“Warning: Null value is eliminated by an aggregate or other SET operation”,尤其在子查询含聚合时 - 不是所有数据库支持 APPLY;它是 SQL Server 和 Oracle(
LATERAL)特有,MySQL、PostgreSQL(旧版)不原生支持
真正容易被忽略的是:APPLY 的“逐行驱动”特性既是优势也是枷锁。它让逻辑清晰,但也把优化器的手脚捆死了——你没法靠改写成 JOIN 就自动获得并行扫描或索引跳过。用之前,先看左表行数、右表数据分布、有没有合适索引支撑子查询的 WHERE 条件。










