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

Jos*_*shG 37 mysql theory oracle 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 
Run Code Online (Sandbox Code Playgroud)

工作范例:

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

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
Run Code Online (Sandbox Code Playgroud)

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

这是什么原因?

ype*_*eᵀᴹ 82

表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  
Run Code Online (Sandbox Code Playgroud)

如果你想找到父母和他们的孩子,你可以INNER JOIN:

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

FROM
        parent  INNER JOIN  child
  ON   parent.id     =    child.pid
Run Code Online (Sandbox Code Playgroud)

结果是,每一个匹配parentid左表和childpid第二个表将显示在结果中的一行:

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  1 | Alex   |   1  | Kate  |
|  1 | Alex   |   1  | Lia   |
|  3 | Cath   |   3  | Mary  |
+----+--------+------+-------+
Run Code Online (Sandbox Code Playgroud)

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

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

FROM
        parent  LEFT JOIN  child
  ON   parent.id    =    child.pid
Run Code Online (Sandbox Code Playgroud)

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

+----+--------+------+-------+
| 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  |
+----+--------+------+-------+
Run Code Online (Sandbox Code Playgroud)

那些NULL来自哪里?好吧,MySQL(或你可能使用的任何其他RDBMS)都不会知道放在那里的东西,因为这些父母没有匹配(孩子),所以没有pid也不能child.name与那些父母匹配.因此,它将这个特殊的非值称为NULL.

我的观点是,这些NULLs是在(在结果集中)创建的LEFT OUTER JOIN.


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

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
Run Code Online (Sandbox Code Playgroud)

结果:

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  2 | Bill   | NULL | NULL  |
|  4 | Dale   | NULL | NULL  |
|  5 | Evan   | NULL | NULL  |
+----+--------+------+-------+
Run Code Online (Sandbox Code Playgroud)

现在,如果我们将该IS NULL检查从WHERE连接ON子句移到连接子句会发生什么?

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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,数据库会尝试从符合这些条件的两个表中查找行.也就是说,parent.id = child.pid AND的child.pid IN NULL.但它找不到这样的匹配,因为no child.pid不能等于某事(1,2,3,4或5)并且同时为NULL!

所以,条件:

ON   parent.id    =    child.pid
AND  child.pid IS NULL
Run Code Online (Sandbox Code Playgroud)

相当于:

ON   1 = 0
Run Code Online (Sandbox Code Playgroud)

这总是如此False.

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

+----+--------+------+-------+
| id | parent | pid  | child | 
+----+--------+------+-------+
|  1 | Alex   | NULL | NULL  |
|  2 | Bill   | NULL | NULL  |
|  3 | Cath   | NULL | NULL  |
|  4 | Dale   | NULL | NULL  |
|  5 | Evan   | NULL | NULL  |
+----+--------+------+-------+
Run Code Online (Sandbox Code Playgroud)

我希望上面的解释清楚.



旁注(与您的问题没有直接关系):为什么Pan我们的JOIN中没有出现?因为在SQL的(不常见的)逻辑中他的pidis NULL和NULL不等于任何东西所以它不能与任何父id(1,2,3,4和5)匹配.即使那里有一个NULL,它仍然不匹配,因为NULL它不等于任何东西,甚至不是NULL它自己(这确实是一个非常奇怪的逻辑!).这就是我们使用特殊支票IS NULL而非= NULL支票的原因.

那么,Pan如果我们做了,会出现RIGHT JOIN吗?是的,它会的!因为RIGHT JOIN将显示匹配的所有结果(我们做的第一个INNER JOIN)加上RIGHT表中不匹配的所有行(在我们的例子中是一行,即(NULL, 'Pan')行).

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

FROM
        parent  RIGHT JOIN  child
  ON   parent.id     =    child.pid
Run Code Online (Sandbox Code Playgroud)

结果:

+------+--------+------+-------+
| id   | parent | pid  | child | 
+---------------+------+-------+
|   1  | Alex   |   1  | Kate  |
|   1  | Alex   |   1  | Lia   |
|   3  | Cath   |   3  | Mary  |
| NULL | NULL   | NULL | Pan   |
+------+--------+------+-------+
Run Code Online (Sandbox Code Playgroud)

不幸的是,MySQL没有FULL JOIN.您可以在其他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 | NULL   | NULL | Pan   |
+------+--------+------+-------+
Run Code Online (Sandbox Code Playgroud)

  • 您可以通过在 id 为“NULL”的“LEFT JOIN”和“RIGHT JOIN”之间采用并集来伪造 MySQL 中的“FULL JOIN”。这有局限性——例如,您无法更新或删除——并且可能带来的麻烦大于其价值。 (2认同)

Sab*_*lik 6

NULL部分是在实际连接之后计算的,因此它需要在where子句中.