MySQL“不在”查询

Posted

技术标签:

【中文标题】MySQL“不在”查询【英文标题】:MySQL "NOT IN" query 【发布时间】:2010-12-03 21:44:55 【问题描述】:

我想运行一个简单的查询来抛出 Table1 的所有行,其中主列值不存在于另一个表 (Table2) 的列中。

我尝试使用:

SELECT * FROM Table1 WHERE Table1.principal NOT IN Table2.principal

这反而会引发语法错误。谷歌搜索把我带到了人们说 mysql 不支持NOT IN 并且需要使用非常复杂的东西的论坛。这是真的?还是我犯了一个可怕的错误?

【问题讨论】:

如果我想要三个表中的相似数据怎么办。我的意思是一个表 1 有 2000 个条目,另外两个表 2 和 3 各有 500 个条目,它们都有共同的字段“名称”。我们如何根据“名称”从表 1 中获取表 2 和 3 中不存在的所有详细信息。我们可以使用 NOT IN 两次吗,如果可以的话如何..? 【参考方案1】:

要使用 IN,您必须有一个集合,请改用以下语法:

SELECT * FROM Table1 WHERE Table1.principal NOT IN (SELECT principal FROM table2)

【讨论】:

小心table2.principal 可以是NULL。在这种情况下,NOT IN 将始终返回FALSE,因为NOT IN 被视为<> ALL,它比较子查询中的所有行,如Table1.principal <> table2.principal,与NULL 比较时失败:Table1.principal <> NULL 不会产生在TRUE。修复:NOT IN (SELECT principal FROM table2 WHERE principal IS NOT NULL). 感谢@Basti 的评论!花了很多时间试图了解为什么查询没有按预期工作。 不要忘记避免在“NOT IN”列表中使用“SELECT *”。您必须选择特定的列。否则你会得到这个错误:***.com/questions/14046838/… @Basti,谢谢,我的 SQL 查询符合您的建议。【参考方案2】:

已经回答了子查询选项,但请注意,在许多情况下,LEFT JOIN 可能是执行此操作的更快方法:

SELECT table1.*
FROM table1 LEFT JOIN table2 ON table2.principal=table1.principal
WHERE table2.principal IS NULL

如果您想检查多个表以确保它不存在于任何表中(如在 SRKR 的评论中),您可以使用这个:

SELECT table1.*
FROM table1
LEFT JOIN table2 ON table2.name=table1.name
LEFT JOIN table3 ON table3.name=table1.name
WHERE table2.name IS NULL AND table3.name IS NULL

【讨论】:

在我自己的测试中,NOT INLEFT JOIN 的性能相同。 +1 两个 一旦查询运行一次,由于内部数据库缓存,您应该得到相同的结果 对我来说性能要好得多。我浏览了不同的表,虽然它们设置了外键。【参考方案3】:

NOT IN vs. NOT EXISTS vs. LEFT JOIN / IS NULL in MySQL

MySQL 以及除 SQL Server 之外的所有其他系统都能够优化 LEFT JOIN / IS NULL 以在找到匹配值后立即返回 FALSE,它是唯一关心记录此行为的系统。 [...] 由于 MySQL 不能使用 HASHMERGE 连接算法,它唯一能够使用的 ANTI JOINNESTED LOOPS ANTI JOIN

[…]

基本上,[NOT IN]LEFT JOIN / IS NULL 使用的计划完全相同,尽管这些计划是由不同的分支执行的代码,它们在EXPLAIN 的结果中看起来不同。算法其实是一样的,查询是在同一时间完成的。

[…]

很难说出[使用NOT EXISTS时性能下降]的确切原因,因为这种下降是线性的,似乎不依赖于数据分布、数量两个表中的值等,只要两个字段都被索引。由于 MySQL 中的三段代码基本上只做一项工作,因此负责EXISTS 的代码可能会进行某种额外的检查,这需要额外的时间。

[…]

MySQL 可以优化所有三种方法来执行NESTED LOOPS ANTI JOIN 的排序。 […] 但是,这三种方法会生成三种不同的计划,由三段不同的代码执行。执行EXISTS 谓词的代码效率降低了大约 30% […]

这就是为什么在 MySQL 中搜索缺失值的最佳方法是使用 LEFT JOIN / IS NULLNOT IN 而不是 NOT EXISTS

(添加了重点)

【讨论】:

【参考方案4】:

不幸的是,MySql 使用“NOT IN”子句似乎是一个问题,下面的屏幕截图显示了返回错误结果的子查询选项:

mysql> show variables like '%version%';
+-------------------------+------------------------------+
| Variable_name           | Value                        |
+-------------------------+------------------------------+
| innodb_version          | 1.1.8                        |
| protocol_version        | 10                           |
| slave_type_conversions  |                              |
| version                 | 5.5.21                       |
| version_comment         | MySQL Community Server (GPL) |
| version_compile_machine | x86_64                       |
| version_compile_os      | Linux                        |
+-------------------------+------------------------------+
7 rows in set (0.07 sec)

mysql> select count(*) from TABLE_A where TABLE_A.Pkey not in (select distinct TABLE_B.Fkey from TABLE_B );
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.07 sec)

mysql> select count(*) from TABLE_A left join TABLE_B on TABLE_A.Pkey = TABLE_B.Fkey where TABLE_B.Pkey is null;
+----------+
| count(*) |
+----------+
|      139 |
+----------+
1 row in set (0.06 sec)

mysql> select count(*) from TABLE_A where NOT EXISTS (select * FROM TABLE_B WHERE TABLE_B.Fkey = TABLE_A.Pkey );
+----------+
| count(*) |
+----------+
|      139 |
+----------+
1 row in set (0.06 sec)

mysql> 

【讨论】:

【参考方案5】:

注意NOT IN不是<> ANY的别名,而是<> ALL的别名!

http://dev.mysql.com/doc/refman/5.0/en/any-in-some-subqueries.html

SELECT c FROM t1 LEFT JOIN t2 USING (c) WHERE t2.c IS NULL

不能被替换为

SELECT c FROM t1 WHERE c NOT IN (SELECT c FROM t2)

你必须使用

SELECT c FROM t1 WHERE c <> ANY (SELECT c FROM t2)

【讨论】:

以上是关于MySQL“不在”查询的主要内容,如果未能解决你的问题,请参考以下文章

mysql查询数值不在表中的sql语句

用于查找一个表中但不在另一个表中的行的 MySQL 查询

MySQL不在子查询不按预期工作

MYSQL查询~ 存在一个表而不在另一个表中的数据

将子查询(不在)重写为加入

MYsql5.7版本之后,用group by查询不在分组字段遇到的坑