Exists and IN
/* 查询来自'Spain'的客户,对每个匹配的客户,执行在Orders.custid列上的索引执行一次查找操作 检查Orders表是否包含具有该客户的custid的订单 子查询中筛选列custid上的索引在这里非常有帮助,因为通过它可以直接访问Orders表中具有特定custid值的行 */ SET STATISTICS TIME ON SELECT custid,companyname FROM Sales.Customers AS C WHERE country=N'Spain' AND custid IN (SELECT custid FROM Sales.Orders) /* 采用Exists */ SELECT * FROM Sales.Customers AS C WHERE EXISTS ( SELECT * FROM Sales.Orders WHERE custid=c.custid ) AND country='Spain' /* 这两个查询哪个效率会更高呢 他们逻辑上是相等的2个查询,优化器会为他们生成相同的计划 也许是测试的表数据太少,好吧我们来建2张大表测试一下 */ --学生表 IF OBJECT_ID('dbo.Sudents') IS NOT NULL DROP TABLE dbo.Sudents CREATE TABLE dbo.Sudents ( id INT IDENTITY(1,1) PRIMARY KEY, NAME VARCHAR(16) NULL ) --学生成绩表 IF OBJECT_ID('dbo.Score') IS NOT NULL DROP TABLE dbo.Score CREATE TABLE dbo.Score ( id INT IDENTITY(1,1) PRIMARY KEY, StudentID INT NULL, Score INT NULL ) --测试数据 DECLARE @_n INT=1000000 DECLARE @_i INT=1 WHILE @_i<@_n BEGIN INSERT INTO dbo.Sudents SELECT 'Student'+CAST(@_i AS VARCHAR(10)) INSERT INTO dbo.Score SELECT @@IDENTITY,CAST(CEILING(RAND()*100) AS INT) SET @_i+=1; END --100W条数据确实太多,执行到20+W的时候暂停 --现在删除学号为201的成绩 INSERT INTO dbo.Score DELETE dbo.Score WHERE StudentID=201 --现在来查询没有成绩的学生 SELECT * FROM dbo.Sudents AS S WHERE NOT EXISTS ( SELECT 1 FROM dbo.Score WHERE StudentID=S.ID ) /* SQL Server 执行时间: CPU 时间 = 264 毫秒,占用时间 = 105 毫秒。 */ SELECT * FROM dbo.Sudents AS S WHERE id NOT IN ( SELECT StudentID FROM dbo.Score ) /* SQL Server 执行时间: CPU 时间 = 297 毫秒,占用时间 = 115 毫秒。 */ --现在看出Not Exists和Not in的区别了 --给Score表的StudentId加上非聚集索引 CREATE NONCLUSTERED INDEX idx_Score_StudentID ON dbo.Score(StudentID) --执行时间 /* SQL Server 执行时间: CPU 时间 = 47 毫秒,占用时间 = 43 毫秒。 SQL Server 执行时间: CPU 时间 = 47 毫秒,占用时间 = 46 毫秒。 */ --加上索引执行时间都缩短了,但两者还是相差不大,那好吧在多删几条记录 DELETE dbo.Score WHERE id IN (202,203,400,600,700,560,951,452) /* SQL Server 执行时间: CPU 时间 = 31 毫秒,占用时间 = 44 毫秒。 (9 行受影响) SQL Server 执行时间: CPU 时间 = 47 毫秒,占用时间 = 46 毫秒。 */ --好吧,not exists和not in 在时间效率上还是有区别的 --现在测试一下exists和in 的区别 --清空Score表 TRUNCATE TABLE Score DECLARE @_m INT=300000 DECLARE @_j INT=210000 WHILE @_j<@_m BEGIN INSERT INTO dbo.Score SELECT @_j,CAST(CEILING(RAND()*100) AS INT) SELECT @_j+=1 END --现在查询存在成绩的学生 INSERT INTO Score SELECT 1000,CAST(CEILING(RAND()*100) AS INT) UNION ALL SELECT 500,CAST(CEILING(RAND()*100) AS INT) UNION ALL SELECT 100,CAST(CEILING(RAND()*100) AS INT) --IN的用法 SET STATISTICS IO OFF SELECT * FROM Sudents S WHERE id IN (SELECT StudentID FROM Score ) --Exists的用法 SELECT * FROM Sudents S WHERE EXISTS ( SELECT 1 FROM score WHERE StudentId =S.ID ) /*虽然2者的执行计划是一样的,但时间上效果还是挺明显的 执行时间 SQL Server 执行时间: CPU 时间 = 31 毫秒,占用时间 = 21 毫秒。 (3 行受影响) SQL Server 执行时间: CPU 时间 = 16 毫秒,占用时间 = 18 毫秒。 */ --可能是上面加了索引的原因 --有人会考虑到主表和从表的大小关系,好吧,就当把子查询的表为从表 --现在主表用20+W条记录,从表有9W条记录 --现在在给从表增加到100W条记录 DECLARE @_l INT=1000000 DECLARE @_k INT=300000 WHILE @_k<@_l BEGIN INSERT INTO Score SELECT @_k,CAST(CEILING(RAND()*100) AS INT) SET @_k+=1 END --好了 在测试上面的语句 /* SQL Server 执行时间: CPU 时间 = 16 毫秒,占用时间 = 21 毫秒。 (3 行受影响) SQL Server 执行时间: CPU 时间 = 31 毫秒,占用时间 = 19 毫秒。 */ --还是Exists的效率还是存在一定效率 /* 为什么IN和Exists会有相同的执行计划呢? 如果考虑一下三值逻辑可能就会意识到他们的区别了,与Exists不同,当输入列表中包含NUll时,In实际上会产生一个UNKOWN的逻辑结果, 例如: a IN (b,c,NULL)的结果是UNKOWN,不过在筛选器中UNKOWN和False处理方式类似,使用IN谓词的查询结果与使用Exists谓词一样, 而却优化器知道这一点所以生成了相同的执行计划,另外NOT IN中包含NULL时,NOT IN 查询总会返回空集。因为 val IN(a,b,c,NULL)永远不会返回False 而是返回TRUE、所以val NOT IN (val1,val2.....,NULL)返回的只有NOTtrue 和not Unkown,这两个结果都不会是true */