NOT EXISTS,其中NULL与其他运算符不一致

时间:2013-01-05 14:56:15

标签: sql sql-server-2008-r2

以下是初始数据:

CREATE TABLE #data
    (
    Id integer,
    Surname varchar(50),
    DOB datetime
    )   
INSERT INTO #data
values 
(1,'smith', null),
(2,'jones', '01 jan 1970'),
(3,'vernon', null),
(4,'smith', '01 jan 1970'),
(5,'jones', '01 jan 1970'),
(6,'vernon', '01 jan 1970'),
(7,null, '01 jan 1970') 

以下是排除列表:

CREATE TABLE #exclusions
    (
    ExcludedSurname varchar(50),
    ExcludedDOB datetime
    )   
INSERT INTO #exclusions
values 
('smith', '01 jan 1970'),
('jones', '01 jan 1970'),
('vernon', null),
(null, '01 jan 1970')   

这是一个返回我意想不到的结果的查询:

SELECT * 
FROM #data a
WHERE
  NOT EXISTS
    (
    SELECT 1
    FROM #exclusions e
    WHERE
            a.DOB = e.ExcludedDOB and
            a.Surname = e.ExcludedSurname
    ) 

为了确保排除Id s 3和7,我可以对脚本进行这种丑陋的更改。生产表中有很多可能的数据(#data的实时版本是1000万条记录) - 这就是为什么我选择了null这么远的替代品。

SELECT * 
FROM #data a
WHERE
  NOT EXISTS
    (
    SELECT 1
    FROM #exclusions e
    WHERE  
            ISNULL(a.DOB, '01 JAN 2200') = ISNULL(e.ExcludedDOB, '01 JAN 2200')  and
            ISNULL(a.Surname,'AAAAAAAAAAAAAAAA') = ISNULL(e.ExcludedSurname,'AAAAAAAAAAAAAAAA')
    )   

此处位于SQL Fiddle

有更优雅的方式来做上述事情吗?

3 个答案:

答案 0 :(得分:6)

在PostgreSQL(SQL Fiddle)中你可以使用

WHERE (a.DOB, a.Surname) IS NOT DISTINCT FROM (e.ExcludedDOB, e.ExcludedSurname)

但是SQL Server缺少两个可以工作的项目。 row value constructorsIS [NOT] DISTINCT FROM

同时您可以使用此处的技术:Undocumented Query Plans: Equality Comparisons

SELECT *
FROM   #data a
WHERE  NOT EXISTS (SELECT *
                   FROM   #exclusions e
                   WHERE  EXISTS (SELECT a.DOB,
                                         a.Surname
                                  INTERSECT
                                  SELECT e.ExcludedDOB,
                                         e.ExcludedSurname)) 

上述内容的变体使用EXCEPT(实例 HERE ):

SELECT *
FROM   #data a
WHERE  EXISTS (SELECT a.DOB, 
                      a.Surname
               EXCEPT
               SELECT e.ExcludedDOB, 
                      e.ExcludedSurname 
               FROM #exclusions e)  

答案 1 :(得分:2)

@MartinSmith

看看这个替代方案:

SELECT *
FROM   #data a
WHERE  EXISTS(SELECT a.Surname,
                     a.DOB
              EXCEPT
              SELECT e.ExcludedSurname,
                     e.ExcludedDOB
              FROM   #exclusions e) 

非常优雅和可读。


虽然为什么在上面的EXISTS中打扰似乎除了做所有的工作?

SELECT Surname,
       DOB
FROM   #data
EXCEPT
SELECT ExcludedSurname,
       ExcludedDOB
FROM   #exclusions 

[提供了我的一位朋友 - 遗憾的是他没有提供SO - 我不知道这一点......

两个备选方案都在SQL FIDDLE

答案 2 :(得分:1)

如果您的排除项没有重复项,则不会将此作为left outer join至少看起来更清晰:

SELECT * 
FROM #data a left outer join
     #exclusions e
     on a.DOB = e.ExcludedDOB and
        a.Surname = e.ExcludedSurname
where e.ExcludedDOB is NULL and e.ExcludedSurname is null

然后,您可以使用coalesce或逻辑处理NULL(此示例显示两者):

SELECT * 
FROM #data a left outer join
     #exclusions e
     on (a.DOB = e.ExcludedDOB or a.DOB is NULL and e.ExcludedDOB is NULL) and
        (coealesce(a.Surname, '<null>') = coalesce(e.ExcludedSurname, '<null>')
where e.ExcludedDOB is NULL and e.ExcludedSurname is null

所有这些方法的缺点是我认为他们不会利用排除表上的索引。 。 。如果桌子很大,这可能是一个好主意。一种方法需要两个连接,但修复了这个问题:

SELECT * 
FROM #data a left outer join
     #exclusions e
     on a.DOB = e.ExcludedDOB and
        a.Surname = e.ExcludedSurname left outer join
     #exclusions enull
     on enull.ExcludedSurname is null and a.Surname is NULL and
        enull.ExcludedDOB = a.DOB
where e.ExcludedDOB is NULL and e.ExcludedSurname is null and
      enull.ExcludedDOB is NULL and enull.ExcludedSurname is null

然而,马丁的方法可能仍然是表现最好的。

如果您不需要来自#data的id,那么最简单的方法是:

select Surname, Dob
from #data
except (select ExcludedSurname, ExcludedDB from #exceptions)

我经常使用这个结构进行表格比较。但是,要获取id,您将在连接中留下NULL问题。