【问题标题】:T-SQL - Left Outer Joins - Filters in the where clause versus the on clauseT-SQL - 左外连接 - where 子句与 on 子句中的过滤器
【发布时间】:2011-02-25 04:19:36
【问题描述】:

我正在尝试比较两个表以在每个表中查找不在另一个表中的行。表 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 字段相当于一个提取的批次ID,我将临时表中的最新提取与昨天存储在分区表中的批次进行比较,该表也包含所有先前提取的批次。创建表 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 子句在一种状态下给了我正确的结果,在另一种状态下给了我正确的结果。我正在寻找一种一致的方式来做到这一点。

在哪里 -

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

【问题讨论】:

  • 为什么将条件从JOIN 子句移到WHERE 子句很重要?当您想要这种类型的行为时,将谓词放在JOIN 中是很正常的事情。
  • 将它移动到 where 子句并不重要,因为对于我将表 1 连接到表 2 的第一个连接,如果我将过滤器放在 on 而不是 in,我会得到不同的结果在哪里。 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 AND table1.groupby = 2 --******** ************************ WHERE --table1.groupby = 2 AND table2.number IS NULL 仅给出 null
  • 那么,你想要哪一个?听起来 NULL-join 符合您的意思。这是执行此类查询的标准方法,通常比子查询替代方案更可取。我认为尝试将条件推入 WHERE 子句没有任何好处。
  • 请检查我添加的编辑部分 - 可能会更好地解释。
  • SQL-92 令人憎恶。

标签: sql left-join


【解决方案1】:

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

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

【讨论】:

  • 该链接已失效。
  • @O.R.Mapper - 由于链接已失效,因此提供了替代答案。如果你觉得有用请点赞stackoverflow.com/a/63735594/6648326我的回答是复制粘贴,但它是主动和努力把它放在这里的。
【解决方案2】:

使用 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 并没有错,这就是你的做法。

【讨论】:

  • 我可能(事后看来是完美的科学)应该把它放在原始问题中,但是如果你看看我上面的评论,你会发现将过滤器从 where 更改为 on first 要么加入给你一个不同的结果。在编码左外连接时,我正在寻找一个“规则”,我应该使用 ON 子句或 where 子句。
  • this onethat blogpost 等各种答案的说法正好相反。我缺少任何上下文差异吗?
【解决方案3】:

在编写查询时,将连接放在 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

【讨论】:

  • +1 理解“ON”子句中的过滤为什么起作用的关键是将过滤器放入这样的内联视图中。
【解决方案4】:
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

【讨论】:

    【解决方案5】:
        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
    

    【讨论】:

      【解决方案6】:

      我自己一直在努力解决这个问题 - 最终是使用 Where 子句从表中选择数据并将其放入临时表中,然后在临时表上使用左外连接。

      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
      

      【讨论】:

        【解决方案7】:

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

        TL;DR:小心左连接,因为左连接查询可能会也可能不会作为左连接执行。这很奇怪,但确实如此。

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

        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');
        

        如果我们想要返回 Table1 中的所有记录而不考虑 Table2 中是否有关联记录,并在有关联记录时显示 Table2 中的数据,我们会编写 LEFT JOIN,如下所示:

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

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

        请注意,我们只获取两个表中存在匹配 ID 的值。这看起来像一个 INNER JOIN,它实际上是作为一个内部连接执行的。要确认这一点,请在运行带有和不带有 WHERE 条件的 LEFT JOIN 之后查看执行计划。 (如果读者想深入了解这个细节,请到original author's blog post

        那么我们怎样才能解决这个问题呢?好吧,既然您首先要进行 LEFT JOIN,那么您显然需要或想要返回 Table1 中的所有记录,而不管 Table2 中的数据如何。如果您真的不想返回 Table2 中某些记录的数据,那么您可以在您的 JOIN 谓词(JOIN 的 ON 部分中的“搜索条件”)中过滤掉这些记录。例如,最后一个查询应该这样写:

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

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

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

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

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

        【讨论】:

          猜你喜欢
          • 2019-02-15
          • 1970-01-01
          • 1970-01-01
          • 2016-07-02
          • 1970-01-01
          • 1970-01-01
          • 2012-01-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多