生成两表笛卡尔积时,CROSS APPLY与OUTER APPLY是否存在差异?
答案是:当两张表之间没有建立任何关联关系、仅通过APPLY生成笛卡尔积时,CROSS APPLY和OUTER APPLY完全没有差异。
虽然OUTER APPLY的设计初衷是当右侧表没有匹配结果时保留左侧表的行,但在无关联条件的场景下,右侧表的结果集是独立于左侧每一行的——要么右侧全量返回,要么因为WHERE过滤全量不返回,根本不存在“某一行左侧有、右侧无匹配”的情况,所以两者的执行逻辑和最终结果会完全一致。
下面是具体的测试验证:
测试环境搭建
先创建两张测试表并插入批量数据,同时创建索引优化查询:
CREATE TABLE dbo.First ( Id INT IDENTITY(1, 1) PRIMARY KEY, Name NVARCHAR(100) ); GO DECLARE @n INT = 1; WHILE @n < 10000 BEGIN INSERT INTO dbo.First (Name) VALUES ('First' + CONVERT(NVARCHAR(100), @n)); SET @n = @n + 1; END GO CREATE INDEX IX__First__Name ON dbo.First(Name); GO CREATE TABLE dbo.Second ( Id INT IDENTITY(1, 1) PRIMARY KEY, Name NVARCHAR(100) ); GO DECLARE @n INT = 1; WHILE @n < 10000 BEGIN INSERT INTO dbo.Second (Name) VALUES ('Second' + CONVERT(NVARCHAR(100), @n)); SET @n = @n + 1; END GO CREATE INDEX IX__Second__Name ON dbo.Second(Name); GO
正常匹配场景测试
使用CROSS APPLY
SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First CROSS APPLY Second WHERE First.Name IN ('First253', 'First3304') AND Second.Name IN ('Second6543', 'Second517');
使用OUTER APPLY
SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First OUTER APPLY Second -- 仅此处修改 WHERE First.Name IN ('First253', 'First3304') AND Second.Name IN ('Second6543', 'Second517');
这两个查询都会返回4行结果(2个First行 × 2个Second行的笛卡尔积),并且查看执行计划会发现完全一致:都是两个索引查找操作接入Nested Loops (Inner Join)。
边界场景测试
我们还测试了多种无匹配的情况,结果也完全一致:
1. First表无匹配
-- CROSS APPLY版本 SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First CROSS APPLY Second WHERE First.Name IN ('no match') AND Second.Name IN ('Second6543', 'Second517'); -- OUTER APPLY版本 SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First OUTER APPLY Second WHERE First.Name IN ('no match') AND Second.Name IN ('Second6543', 'Second517');
两者都返回0行,因为First表没有符合条件的行,自然不会产生任何笛卡尔积。
2. Second表无匹配
-- CROSS APPLY版本 SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First CROSS APPLY Second WHERE First.Name IN ('First253', 'First3304') AND Second.Name IN ('no match'); -- OUTER APPLY版本 SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First OUTER APPLY Second WHERE First.Name IN ('First253', 'First3304') AND Second.Name IN ('no match');
两者同样返回0行,因为Second表没有符合条件的行,即便First有匹配行,也没有可关联的右侧数据。
3. 两表均无匹配
-- CROSS APPLY版本 SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First CROSS APPLY Second WHERE First.Name IN ('no match') AND Second.Name IN ('no match'); -- OUTER APPLY版本 SELECT First.Id AS FirstId, Second.Id AS SecondId FROM First OUTER APPLY Second WHERE First.Name IN ('no match') AND Second.Name IN ('no match');
结果还是0行,逻辑和上面一致。
总结
在这种无关联条件的笛卡尔积场景下,CROSS APPLY和OUTER APPLY的行为完全等价。因为OUTER APPLY的“保留左侧无匹配行”特性只有在右侧结果集依赖左侧行(比如有Second.SomeCol = First.SomeCol这类关联条件)时才会生效,而没有关联条件时,右侧结果集是全局的,要么全有要么全无,不会出现单行左侧有、右侧无的情况,所以两者没有任何差异。
内容的提问来源于stack exchange,提问作者T.J. Crowder

