【问题标题】:SQL Join with NOT IN() does not work使用 NOT IN() 的 SQL 连接不起作用
【发布时间】:2017-06-14 15:48:05
【问题描述】:

我有 2 个包含相同键 p_id 的表:

test1                       test2
+-------------+             +----------------------+
| p_id | name |             | o_id | name  |  p_id |
+-------------+             +----------------------+
|  1   | Paul |             |  1   | London |   1  |
|  2   | Marc |             |  2   | Paris  |   1  |        
+-------------+             +----------------------+

现在我想从 test1 中获取与 test2 无关的所有条目。 在上面的示例中,我已经抽象了我的表,所以RIGHT JOIN 是不可能的(实际上我必须加入 4 个表)。

SELECT a.*,b.*
FROM test1 a
LEFT JOIN test2 b
ON a.p_id=b.p_id
WHERE b.p_id NOT IN(SELECT DISTINCT p_id FROM test2);

我希望有p_id=2 的一行。但是我得到一个空的结果。 当我将代码更改为:

SELECT a.*,b.*
FROM test1 a
LEFT JOIN test2 b
ON a.p_id=b.p_id
WHERE a.p_id NOT IN(SELECT DISTINCT p_id FROM test2);

然后它工作正常。但为什么?我认为首先处理LEFT JOIN(结果为1行),然后处理WHERE(在test2中未找到p_id,所以b.p_idnull-null不在子选择中- 结果仍然是 1 行)。

有人能解释一下这种行为吗?

【问题讨论】:

  • 我没有发现任何问题。您的 LEFT JOIN 在第二个表中获得 2 行 p_id=1 但随后您将子查询中的那些排除在外
  • @RaymondNijland :抱歉,这是不正确的。我已经检查了您的建议,现在我得到了 2 行,其中设置了 p_id 和 test1.name,其他列为空。
  • @Mihai :这是不正确的。 Left Join 的结果是 3 行(2 行 p_id=1 和 1 行 p_id=2),然后我排除了 p_id=1 的两者。
  • @RaymondNijland 在左连接的 rhs 表上具有 where 条件不会自动将左连接变为内部连接;只有需要 rhs 表中的非空值的条件才会这样做。
  • @Uueerdo 您的评论通常是正确的,但在这种情况下,第一个 SQL 语句中外连接到表的谓词确实是“将左连接变为内连接”,因为 NULL(返回从外部连接到不匹配的行)既不在 p_id 列表中,也不在 p_id 列表中。 (试试这个插图:当 null 不在 (1, 2, 3) 中时选择大小写,然后在 (1, 2, 3) 中为 null 时选择大小写,然后在 (1, 2, 3) 中选择大小写,然后在‘in’ else 'neither' end 中选择 case)

标签: mysql sql database join


【解决方案1】:

这与在比较中如何处理 NULL 有关。

要测试/查看,您可以运行简单的查询,例如:

选择 1 从双 WHERE NULL = NULL;

选择 1 从双 WHERE NULL NOT IN (1, 2, 3);

都不返回一行,因为这两个条件都返回“不正确”的 NULL。

【讨论】:

  • 你知道这是否是特定于 MySql 的吗?或者这也适用于例如甲骨文?
  • 我已经有一段时间没有玩 Oracle 了,然后它只用于一门大学课程。我相信 MySQL 中可能有设置来改变这种行为,所以即使是 MySQL 也可能并非总是如此。与这些类似的查询应该可以有效地解决几乎任何 SQL RDBMS 的问题。
【解决方案2】:

正如 Uueerdo 所说,这是一个 NULL 比较问题。但除此之外,你真的应该使用反连接:

SELECT a.*,b.*
FROM test1 a
LEFT JOIN test2 b
ON a.p_id=b.p_id
WHERE b.p_id IS NULL;

它更清洁,通常更高效。

【讨论】:

【解决方案3】:

NOT IN 没有问题,因为您在Where 子句中过滤了正确的表,所以它被隐式转换为INNER JOIN

没有Where子句结果会是这样的

+------+------+--------+--------+--------+
| p_id | name |  o_id  |  name  |  p_id  |
+------+------+--------+--------+--------+
|    1 | Paul | 1      | London | 1      |
|    1 | Paul | 2      | Paris  | 1      |
|    2 | Marc | (null) | (null) | (null) |
+------+------+--------+--------+--------+

如果您正在应用过滤器,则在此

WHERE b.p_id NOT IN(SELECT DISTINCT p_id FROM test2);

子查询返回1,它出现在上述结果的最后一列中。所以你没有得到任何结果。

如果您想知道为什么没有返回最后一条具有 NULL 的记录,因为它不是 1。这是因为NULL无法使用=INNOT IN等进行比较。我们需要使用IS运算符来检查NULL

正确的做法是使用NOT EXISTS。也处理 NULL

select * 
from test1 a
Where Not Exists (select 1 from test2 b Where a.p_id = b.p_id)

【讨论】:

  • Not in 不能很好地处理空值。此外,在左连接右侧的表上使用 where 子句会否定左连接,使其像内部连接一样。最好使用反连接或不存在。
  • @xQbert - 我没有提到它作为答案。刚刚解释了为什么它没有返回结果。
  • @xQbert - 更新了我的答案
【解决方案4】:

一般来说,先执行WHERE,再执行JOIN。此外,当您使用 LEFT JOIN 时,它是包含所有内容的 LEFT 表,因此您不应该期望在 JOIN 之后有一行 p_id=2,正如您所说。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-30
  • 2013-09-18
  • 1970-01-01
  • 2016-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多