我在工作中重构一些sql,偶然发现了一些我不确定如何解释的东西。我认为有两个查询会产生相同的结果,但不会,我不知道为什么。
查询如下:
select *
from TableA as a
left join TableB b on a.id = b.id and b.status in (10, 100)
select *
from TableA as a
left join TableB b on a.id = b.id
where b.status is null or b.status in (10, 100)
什么时候这些不会返回相同的结果?
与where条件b.status is null or b.status in (10, 100)
的最大区别在于b.status是1和b.id = a.id
在第一个查询中,您仍将获得表A中的行,其中相应的B部分为NULL,因为On条件未完全满足。在第二个查询中,您将在JOIN中获取a和b表中的行,这些行将在where子句中丢失。
让我举个例子:
SELECT * INTO #A FROM (VALUES
(1),(2),(3),(4)) T(id)
SELECT * INTO #B FROM (VALUES
(1,NULL),
(2,1),
(3,10)) T(id,status)
select *
from #A as a
left join #B b on a.id = b.id and b.status in (10, 100)
select *
from #A as a
left join #B b on a.id = b.id
where b.status is null or b.status in (10, 100)
结果
id id status
----------- ----------- -----------
1 NULL NULL
2 NULL NULL
3 3 10
4 NULL NULL
id id status
----------- ----------- -----------
1 1 NULL
3 3 10
4 NULL NULL
最终答复:
从逻辑上讲,您的第二个查询几乎与以下内容相同:
SELECT *
FROM TableA as a
LEFT JOIN TableB b
ON a.id = b.id
WHERE b.status IN (10, 100); -- b.status is null has been removed
那么问题归结为ON
子句中的过滤的标准问题与WHERE
子句中的过滤。在前一种情况下,即使ON
逻辑失败,也会保留连接左侧的所有记录。在后一种情况下,即第二次查询的情况,匹配的status
条件失败的记录将被删除,并且不会显示在结果集中。
我说几乎一样,因为b.status IS NULL
检查你实际上允许记录存活,在连接条件下确实匹配,但恰好有null
值为status
。但是,除此之外,你的问题实际上只是在ON
条款中过滤而不是在WHERE
条款中进行过滤。
在正常情况下,A LEFT JOIN
或LEFT OUTER JOIN
为左表中的所有行提供来自两个表的匹配行。当左表中的行在右表中没有匹配的行时,关联的结果集行包含来自右表的所有选择列表列的空值。
当我们使用左外连接添加where子句时,它的行为类似于内连接,其中过滤器在ON子句之后应用,仅显示那些具有的行
B.status is null or 10 or 100
由于它是LEFT JOIN
,不匹配的ON
条件将简单地为右手表中的列生成NULL值。
另一方面,不匹配的WHERE
子句将完全消除该行,而不管连接类型如何。考虑这个例子:
CREATE TABLE #TableA(id INT);
INSERT INTO #TableA VALUES
(1),
(2);
CREATE TABLE #TableB(id INT, status INT);
INSERT INTO #TableB VALUES
(1, 10),
(2, -1);
SELECT *
FROM #TableA AS A
LEFT JOIN #TableB B ON A.id = B.id AND B.status IN (10)
/*
a.id | b.id | status
1 | 1 | 10
2 | NULL | NULL
*/
SELECT *
FROM #TableA AS A
LEFT JOIN #TableB B ON A.id = B.id
-- WHERE B.status IS NULL OR B.status IN (10)
/*
a.id | b.id | status
1 | 1 | 10
2 | 2 | -1
*/
请注意,我已在第二个查询中注释掉了where子句(结果已经不同)。一旦添加,它也将消除第二行。
select * from TableA as a left join TableB b on a.id = b.id and b.status in (10, 100)
在连接发生之前评估条件语句AND。
select * from TableA as a left join TableB b on a.id = b.id
where b.status is null or b.status in (10, 100)
过滤后会发生过滤。
这就是你得到不同输出的原因。
在第一个查询中,您将获得左表的所有行
但
在第二种情况下,当你按where子句过滤时,它只会给那些完全填充where条件的记录