EXISTS
至少在SQL Server中,重写该子句可以使查询更整洁,并且可能减少误导的一种有趣的方式可能是:
SELECT a, b, c
FROM a_table
WHERE b = ANY
(
SELECT b
FROM another_table
);
的反半连接版本如下所示:
SELECT a, b, c
FROM a_table
WHERE b <> ALL
(
SELECT b
FROM another_table
);
这两种通常被优化到相同的计划,WHERE EXISTS
或WHERE NOT EXISTS
,但我们的目的是明确无误的,你有没有“怪” 1
或*
。
有趣的是,与关联的null检查问题对于来说NOT IN (...)
是有问题的<> ALL (...)
,而NOT EXISTS (...)
不会受到该问题的困扰。考虑以下两个具有可空列的表:
IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
DROP TABLE #t;
END;
CREATE TABLE #t
(
ID INT NOT NULL IDENTITY(1,1)
, SomeValue INT NULL
);
IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
DROP TABLE #s;
END;
CREATE TABLE #s
(
ID INT NOT NULL IDENTITY(1,1)
, SomeValue INT NULL
);
我们将向两者添加一些数据,其中某些行匹配,而某些行不匹配:
INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);
SELECT *
FROM #t;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);
SELECT *
FROM #s;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +
该NOT IN (...)
查询:
SELECT *
FROM #t
WHERE #t.SomeValue NOT IN (
SELECT #s.SomeValue
FROM #s
);
有以下计划:
由于NULL值使相等性无法确认,因此查询不返回任何行。
该查询<> ALL (...)
显示了相同的计划,并且不返回任何行:
SELECT *
FROM #t
WHERE #t.SomeValue <> ALL (
SELECT #s.SomeValue
FROM #s
);
使用的变体NOT EXISTS (...)
显示了稍微不同的计划形状,并且确实返回了行:
SELECT *
FROM #t
WHERE NOT EXISTS (
SELECT 1
FROM #s
WHERE #s.SomeValue = #t.SomeValue
);
计划:
该查询的结果:
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
这使得使用<> ALL (...)
和一样容易出现有问题的结果NOT IN (...)
。
EXISTS (SELECT FROM ...)
。