在连接条件下使用IS NULL或IS NOT NULL - 理论问题

时间:2011-07-08 21:48:13

标签: mysql oracle theory left-join

理论问题在这里:

为什么指定table.field IS NULL或table.field IS NOT NULL不能在连接条件(例如,左连接或右连接)上工作,而只能在where条件下工作?

非工作示例:

- 这应该返回所有已过滤掉任何退货(非空值)的货件。但是,无论是否满足[r.id is null]语句,都会返回所有货件。

SELECT
  *
FROM 
  shipments s
LEFT OUTER JOIN returns r  
  ON s.id = r.id
  AND r.id is null
WHERE
  s.day >= CURDATE() - INTERVAL 10 DAY 

工作示例:

- 返回正确的行数,即总出货量,减去与退货相关的任何行数(非空值)。

SELECT
  *
FROM 
  shipments s
LEFT OUTER JOIN returns r  
  ON s.id = r.id
WHERE
  s.day >= CURDATE() - INTERVAL 10 DAY
  AND r.id is null

为什么会这样?正在连接的两个表之间的所有其他过滤条件都可以正常工作,但由于某种原因,除非在where语句中,否则IS NULL和IS NOT NULL过滤器不起作用。

这是什么原因?

6 个答案:

答案 0 :(得分:81)

表A和B的示例:

 A (parent)       B (child)    
============    =============
 id | name        pid | name 
------------    -------------
  1 | Alex         1  | Kate
  2 | Bill         1  | Lia
  3 | Cath         3  | Mary
  4 | Dale       NULL | Pan
  5 | Evan  

如果您想找到父母及其子女,请执行INNER JOIN

SELECT id,  parent.name AS parent
     , pid, child.name  AS child

FROM
        parent  INNER JOIN  child
  ON   parent.id     =    child.pid

结果是,左表中parent的{​​{1}}和第二个表中id的{​​{1}}的每次匹配都会显示为一行结果:

child

现在,上面没有显示没有孩子的父母(因为他们的id在孩子的id中没有匹配,所以你做什么?你做了一个外连接。有三种类型的外连接,左边,右边和全外连接。我们需要左边的一个,因为我们想要左表(父)的“额外”行:

pid

结果是,除了以前的比赛,所有没有比赛的父母(阅读:没有孩子)也会显示:

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  1 | Alex   |   1  | Kate  |
|  1 | Alex   |   1  | Lia   |
|  3 | Cath   |   3  | Mary  |
+----+--------+------+-------+

所有SELECT id, parent.name AS parent , pid, child.name AS child FROM parent LEFT JOIN child ON parent.id = child.pid 来自哪里?好吧,MySQL(或你可能使用的任何其他RDBMS)都不会知道放在那里的东西,因为这些父母没有匹配(孩子),所以没有+----+--------+------+-------+ | id | parent | pid | child | +----+--------+------+-------+ | 1 | Alex | 1 | Kate | | 1 | Alex | 1 | Lia | | 3 | Cath | 3 | Mary | | 2 | Bill | NULL | NULL | | 4 | Dale | NULL | NULL | | 5 | Evan | NULL | NULL | +----+--------+------+-------+ 也没有NULL来匹配那些父母。因此,它将这个特殊的非值称为pid

我的观点是child.name期间(在结果集中)创建了这些NULL


因此,如果我们只想显示没有孩子的父母,我们可以在NULLs上面添加LEFT OUTER JOINWHERE child.pid IS NULL 完成后评估(检查)LEFT JOIN子句。因此,从上面的结果可以清楚地看出,只显示WHERE为NULL的最后三行:

JOIN

结果:

pid

现在,如果我们将SELECT id, parent.name AS parent , pid, child.name AS child FROM parent LEFT JOIN child ON parent.id = child.pid WHERE child.pid IS NULL 检查从+----+--------+------+-------+ | id | parent | pid | child | +----+--------+------+-------+ | 2 | Bill | NULL | NULL | | 4 | Dale | NULL | NULL | | 5 | Evan | NULL | NULL | +----+--------+------+-------+ 移到加入IS NULL子句会发生什么?

WHERE

在这种情况下,数据库会尝试从符合这些条件的两个表中查找行。也就是说,ON AND SELECT id, parent.name AS parent , pid, child.name AS child FROM parent LEFT JOIN child ON parent.id = child.pid AND child.pid IS NULL 的行。但它可以找到没有这样的匹配,因为没有parent.id = child.pid可以等于某事(1,2,3,4或5)并且同时为NULL!

所以,条件:

child.pid IN NULL

相当于:

child.pid

总是ON parent.id = child.pid AND child.pid IS NULL

那么,为什么它会从左表中返回所有行? 因为它是LEFT JOIN!并且左连接返回匹配的行(在这种情况下为无)以及左表中不匹配的行检查(在这种情况下全部):

ON   1 = 0

我希望上面的解释清楚。



旁注(与您的问题没有直接关系):为什么False没有出现在我们的JOIN中?因为他的+----+--------+------+-------+ | id | parent | pid | child | +----+--------+------+-------+ | 1 | Alex | NULL | NULL | | 2 | Bill | NULL | NULL | | 3 | Cath | NULL | NULL | | 4 | Dale | NULL | NULL | | 5 | Evan | NULL | NULL | +----+--------+------+-------+ Pan并且SQL的(不常见)逻辑中的NULL不等于任何东西所以它不能与任何父ID(1,2,3, 4和5)。即使那里有一个NULL,它仍然不匹配,因为pid不等于任何东西,甚至不是NULL本身(这确实是一个非常奇怪的逻辑!)。这就是为什么我们使用特殊支票NULL而不是NULL支票。

那么,如果我们IS NULL= NULL会出现吗?是的,它会的!因为RIGHT JOIN将显示所有匹配的结果(我们做的第一个INNER JOIN)以及RIGHT表中不匹配的所有行(在我们的例子中是{1}}行。

Pan

结果:

RIGHT JOIN

不幸的是,MySQL没有(NULL, 'Pan')。您可以在其他RDBMS中尝试它,它将显示:

SELECT id,  parent.name AS parent
     , pid, child.name  AS child

FROM
        parent  RIGHT JOIN  child
  ON   parent.id     =    child.pid

答案 1 :(得分:6)

NULL部分是在实际连接之后计算的,所以这就是为什么它需要在where子句中。

答案 2 :(得分:3)

实际上没有忽略NULL过滤器。这就是加入两个表的工作方式。

我将尝试使用数据库服务器执行的步骤来理解它。 例如,当您执行您所说的忽略NULL条件的查询时。 选择   * 从   发货 LEFT OUTER JOIN返回r
  ON s.id = r.id.   AND r.id为null 哪里   s.day> = CURDATE() - INTERVAL 10 DAY

首先发生的事情是表SHIPMENTS中的所有行都被选中

在下一步数据库服务器上将开始从第二个(RETURNS)表中逐个选择记录。

在第三步,RETURNS表中的记录将根据您在查询中提供的连接条件进行限定,在这种情况下是(s.id = r.id且r.id为NULL)

请注意,在第三步中应用的此限定仅决定服务器是应接受还是拒绝RETURNS表的当前记录,以附加所选的SHIPMENT表行。它绝不会影响从SHIPMENT表中选择记录。

一旦服务器完成了连接两个表,其中包含SHIPMENT表的所有行和RETURNS表的选定行,它将在中间结果上应用where子句。 所以当你在where子句中放入(r.id为NULL)条件时,r.id = null的中间结果中的所有记录都会被过滤掉。

答案 3 :(得分:2)

在处理WHERE条件后评估JOIN子句。

答案 4 :(得分:2)

你正在做一个LEFT OUTTER JOIN,表示你想要语句左边的表中的每个元组,无论它在RIGHT表中是否有匹配的记录。在这种情况下,您的结果将从RIGHT表中删除,但结果与您在ON子句中未包含AND的结果相同。

在WHERE子句中执行AND会导致在LEFT JOIN发生后进行修剪。

答案 5 :(得分:1)

你的执行计划应该明确这一点; JOIN优先,之后结果被过滤。