CROSS APPLY 和 OUTER APPLY 是解决多行返回与逐行传参问题的必要工具,非语法糖;标量子查询性能差时应改用 OUTER APPLY 配合索引;字符串拆分、JSON 解析、TVF 调用必须用 CROSS APPLY 或 OUTER APPLY,并注意别名、排序、NULL 处理等细节。

CROSS APPLY 和 OUTER APPLY 不是“替代嵌套子查询”的语法糖,而是解决特定语义问题的必要工具——当右侧需要返回多行、或必须逐行传参调用表值函数时,相关子查询根本无法表达。
标量子查询在 SELECT 里跑得慢?优先换 OUTER APPLY
WHERE 或 SELECT 中写 (SELECT TOP 1 order_date FROM orders WHERE user_id = u.id ORDER BY order_date DESC),SQL Server 很可能为每一行都全表扫描 orders 表,执行计划里堆满 Compute Scalar 节点。
- 改用
OUTER APPLY后,优化器更倾向生成 Nested Loops + Index Seek,前提是orders(user_id, order_date)有复合索引 - 必须显式给子查询加别名(如
AS last_order),且字段要命名(order_date AS last_order),否则外层无法引用 -
TOP 1和ORDER BY必须共存;单独写TOP 1不保证取到最新记录 - 若业务允许用户无订单时显示 NULL,用
OUTER APPLY;若只关心有订单的用户,才考虑CROSS APPLY
字符串拆分、JSON 解析必须用 CROSS APPLY
想把 tags NVARCHAR(MAX) 字段(值为 'A,B,C')展开成三行,写 (SELECT value FROM STRING_SPLIT(tags, ',')) 在 SELECT 列表会报错:Subquery returned more than 1 value。
-
STRING_SPLIT()返回表,不是标量,只能放在FROM子句中,配合CROSS APPLY使用:CROSS APPLY STRING_SPLIT(tags, ',') AS t - SQL Server 2016+ 的
STRING_SPLIT默认不保序,需用三参数版:STRING_SPLIT(tags, ',', 1),再ORDER BY t.ordinal对齐原始位置 - 解析 JSON 数组(如
items NVARCHAR(MAX)值为[{"id":1,"qty":2}])必须用:CROSS APPLY OPENJSON(items) WITH (id INT '$.id', qty INT '$.qty');直接写JSON_VALUE(items, '$[0].id')无法遍历全部元素 - 若
items为 NULL 或空字符串,CROSS APPLY会跳过整行;需保留左表行,改用OUTER APPLY
调用自定义表值函数(TVF)时别漏掉别名和括号规则
写 CROSS APPLY dbo.SplitString(u.name, ' ') AS s 是对的;但 CROSS APPLY (dbo.SplitString(u.name, ' ')) AS s 或 CROSS APPLY dbo.SplitString(u.name, ' ')(缺 AS)都会报错。
- TVF 调用本身已返回表,无需额外括号包裹;多余括号触发
Incorrect syntax near '(' -
AS关键字不可省略,哪怕只写AS s—— 这是 SQL Server 强制要求,不是风格问题 - 右侧表达式必须是“可被当作临时表看待”的结构化结果,不能是纯标量表达式(如
CROSS APPLY (SELECT GETDATE())合法,但没意义;而CROSS APPLY GETDATE()直接语法错误) - 如果 TVF 内部逻辑复杂、耗资源,且被多次引用,
CROSS APPLY会重复执行;此时应先用 CTE 物化结果,再 JOIN
最容易被忽略的是语义陷阱:CROSS APPLY 不是 JOIN 的等价写法,它强制“每行独立执行右侧”,左侧数据量大 + 右侧无索引 = 性能雪崩。别为了“看着高级”硬套,先看执行计划里的 Nested Loops 是否真走索引查找,而不是每次都扫全表。

















