T-SQL - 左外连接 - where子句与on子句中的过滤器

时间:2010-05-28 14:51:12

标签: sql left-join

我试图比较两个表来查找每个表中不在另一个表中的行。表1有一个groupby列,用于在表1中创建2组数据。

groupby     number
----------- -----------
1           1
1           2
2           1
2           2
2           4

表2只有一列。

number
-----------
1
3
4

因此,表1在组2中具有值1,2,4,而表2具有值1,3,4。

我在加入第2组时期望得到以下结果:

`Table 1 LEFT OUTER Join Table 2`
T1_Groupby  T1_Number   T2_Number
----------- ----------- -----------
2           2           NULL

`Table 2 LEFT OUTER Join Table 1`
T1_Groupby  T1_Number   T2_Number
----------- ----------- -----------
NULL        NULL        3

我能让它工作的唯一方法是,如果我为第一个连接添加了一个where子句:

PRINT 'Table 1 LEFT OUTER Join Table 2, with WHERE clause'
select  table1.groupby as [T1_Groupby],
        table1.number as [T1_Number],
        table2.number as [T2_Number]
from    table1
        LEFT OUTER join table2
        --******************************
        on table1.number = table2.number
        --******************************
WHERE   table1.groupby = 2
    AND table2.number IS NULL

和第二个ON的过滤器:

PRINT 'Table 2 LEFT OUTER Join Table 1, with ON clause'
select  table1.groupby as [T1_Groupby],
        table1.number as [T1_Number],
        table2.number as [T2_Number]
from    table2
        LEFT OUTER join table1
            --******************************
            on table2.number = table1.number
            AND table1.groupby = 2
            --******************************
WHERE   table1.number IS NULL

任何人都可以想出一种不在on子句中但在where子句中使用过滤器的方法吗?

这个上下文是我在数据库中有一个临时区域,我想识别已删除的新记录和记录。 groupby字段相当于提取的batchid,我将临时表中的最新提取与昨天存储在partioneds表中的批处理进行比较,该表还包含所有先前提取的批处理。创建表1和2的代码:

create table table1 (number int, groupby int)
create table table2 (number int)
insert into table1 (number, groupby) values (1, 1)
insert into table1 (number, groupby) values (2, 1)
insert into table1 (number, groupby) values (1, 2)
insert into table2 (number) values (1)
insert into table1 (number, groupby) values (2, 2)
insert into table2 (number) values (3)  
insert into table1 (number, groupby) values (4, 2)  
insert into table2 (number) values (4)  

编辑:

更多上下文 - 取决于我放置过滤器的位置我得到不同的结果。如上所述,where子句在一个状态下给出了正确的结果,在另一个状态下给出了ON。我正在寻找一种一贯的做法。

哪里 -

select  table1.groupby as [T1_Groupby],
        table1.number as [T1_Number],
        table2.number as [T2_Number]
from    table1
        LEFT OUTER join table2
            --******************************
            on table1.number = table2.number
            --******************************
WHERE   table1.groupby = 2 
    AND table2.number IS NULL

结果:

T1_Groupby  T1_Number   T2_Number
----------- ----------- -----------
2           2           NULL

开 -

select  table1.groupby as [T1_Groupby],
        table1.number as [T1_Number],
        table2.number as [T2_Number]
from    table1
        LEFT OUTER join table2
            --******************************
            on table1.number = table2.number
            AND table1.groupby = 2
            --******************************
WHERE   table2.number IS NULL

结果:

T1_Groupby  T1_Number   T2_Number
----------- ----------- -----------
1           1           NULL
2           2           NULL
1           2           NULL

其中(本次表2) -

select  table1.groupby as [T1_Groupby],
        table1.number as [T1_Number],
        table2.number as [T2_Number]
from    table2
        LEFT OUTER join table1
            --******************************
            on table2.number = table1.number
            AND table1.groupby = 2
            --******************************
WHERE   table1.number IS NULL

结果:

T1_Groupby  T1_Number   T2_Number
----------- ----------- -----------
NULL        NULL        3

开 -

select  table1.groupby as [T1_Groupby],
        table1.number as [T1_Number],
        table2.number as [T2_Number]
from    table2
        LEFT OUTER join table1
            --******************************
            on table2.number = table1.number
            --******************************
WHERE   table1.number IS NULL
    AND table1.groupby = 2

结果:

T1_Groupby  T1_Number   T2_Number
----------- ----------- -----------
(0) rows returned

7 个答案:

答案 0 :(得分:12)

如果在WHERE子句中过滤左外连接表,则实际上是在创建内连接

另请参阅此Wiki页面:WHERE conditions on a LEFT JOIN

答案 1 :(得分:5)

使用LEFT OUTER JOINS,您必须在ON子句中过滤或使用:

WHERE
    (LeftJoinTable.ID IS NULL OR LeftJoinTable.Col1=YourFilter)

如果您只是在WHERE中过滤:

WHERE 
    LeftJoinTable.Col1=YourFilter

只要没有LeftJoinTable.ID(使连接成为INNER JOIN),您将丢弃父加入行。

通过将过滤器置于ON中,可以消除LEFT JOIN行,但不会消除父加入行,这就是它的工作原理。

编辑基地不要OP的评论
过滤LEFT OUTER JOIN表的唯一方法是在ON子句中,除非您想在上面的第一个代码示例中使用像我所示的OR。在ON子句中过滤LEFT OUTER JOIN没有错,这就是你的做法。

答案 2 :(得分:2)

在编写查询时,将连接放在ON子句中是有意义的,因为您特别只想连接表1中组'2'中的值。

另一种方法是将table1预过滤到您感兴趣的组,例如

select  t1Group.groupby,
        t1Group.number as [T1_Number],
        table2.number as [T2_Number]
from    table2
        LEFT OUTER join (SELECT * FROM table1 WHERE groupby=2) t1Group
            on table2.number = t1Group.number
WHERE   t1Group.number IS NULL

答案 3 :(得分:0)

SELECT  dbo.table1.groupby as [T1_Groupby],
        dbo.table1.number as [T1_Number],
        t21.number as [t21_Number]
FROM    dbo.table1
LEFT OUTER join dbo.table2 t21
    ON dbo.table1.number = t21.number
LEFT OUTER join dbo.table2 t22
    ON dbo.table1.groupby= t22.number
WHERE t21.number is null AND t22.number is null

答案 4 :(得分:0)

    select  dbo.table1.groupby as [T1_Groupby],
                            dbo.table1.number as [T1_Number],
                            t22.number as [t22_Number]

                    from    dbo.table1 right outer join 
                    (select  dbo.table1.groupby,
                            dbo.table2.number as number

                    from    dbo.table1
                    right OUTER join dbo.table2
                    on dbo.table1.number = dbo.table2.number

                    where dbo.table1.number is null) t22
                    on dbo.table1.groupby = t22.number
                    where dbo.table1.groupby is null

答案 5 :(得分:0)

我一直在努力解决这个问题 - 最后一天是从Where子句中选择表中的数据并将其放入临时表中,然后在Temp Table上使用Left outer join。

SELECT table1.GroupBy, table1.number INTO #Temp FROM table1 WHere GroupBy = 2
SELECT table2.Groupby, #temp.number From table2 LEFT OUTER JOIN #temp on table2.Groupby = #temp.Groupby

答案 6 :(得分:0)

顶部答案中的链接不再起作用。这是另一个link/blog post,回答了这个问题。我已在此处复制粘贴了该链接的主要内容(不是所有内容),这样我们就不会丢失该链接也不再起作用的知识。

TL; DR:注意左连接,因为LEFT JOIN查询可能会也可能不会作为LEFT JOIN执行。它很奇怪,但是是真的。

创建样本测试数据:(在临时表中)

DECLARE @Table1 TABLE (colID int, colVal varchar(5));
DECLARE @Table2 TABLE (columnID int, columnVal varchar(15));
  
INSERT @Table1 VALUES (1,'one'),(2,'two'),(3,'three'),(4,'four'),(5,'five');
INSERT @Table2 VALUES (1,'some value'),(3,'blah blah blah'),(5,'hello world'),(12,'howdy');

如果我们要返回表1中的所有记录,而不管表2中是否有关联记录,并在有关联记录时显示表2中的数据,则可以编写LEFT JOIN,如下所示:

SELECT *
  FROM @Table1 tb1
        LEFT OUTER JOIN @Table2 tb2
          ON tb1.colID = tb2.columnID;

enter image description here

但是,如果我们现在想向查询中添加WHERE子句以仅从ID小于4的Table2中获取数据,我们可以执行以下操作:

enter image description here

请注意,我们仅在两个表中都有匹配ID的情况下获取值。看起来像一个INNER JOIN,实际上是作为一个内部联接执行的。要确认这一点,请在具有和不具有WHERE条件的情况下运行LEFT JOIN之后,请参阅执行计划。 (如果读者想了解这个细节,请转到original author's blog post

那么我们如何解决这个问题?好吧,由于您首先要进行LEFT JOIN操作,因此无论表2中的数据如何,您显然都需要或希望返回表1中的所有记录。而且,如果您确实不想返回Table2中某些记录的数据,则可以在JOIN谓词(JOIN的ON部分中的“搜索条件”)中过滤掉这些记录。例如,最后一个查询将这样写:

SELECT *
  FROM @Table1 tb1
        LEFT OUTER JOIN @Table2 tb2
          ON tb1.colID = tb2.columnID
         AND tb2.columnID < 4;

enter image description here

看到我们仍然从表1中获得5条记录,但从表2中获得不符合我们标准的数据。它只为该表中不符合条件的数据返回NULL。我们可以再次查看执行计划,以证明我们实际上正在使用LEFT JOIN。 (如果读者想了解这个细节,请转到original author's blog post

更多说明: 何时有人使用IS NULL条件而不是值呢?好吧,在那种情况下,您可以在WHERE子句中使用它。

如果我们改用IS NOT NULL怎么办?它将执行INNER JOIN,就像使用实际值一样。

我们可以通过查看original author's blog post中提到的执行计划来验证上述主张。