如何从一个表中选择另一张表中不存在的所有记录?

Posted

技术标签:

【中文标题】如何从一个表中选择另一张表中不存在的所有记录?【英文标题】:How to select all records from one table that do not exist in another table? 【发布时间】:2010-04-21 20:18:25 【问题描述】:

table1 (id, name) table2 (id, name)

查询:

SELECT name   
FROM table2  
-- that are not in table1 already

【问题讨论】:

查看底部带有 UNION 的解决方案,它比此处列出的任何其他解决方案都要快几个数量级。 【参考方案1】:
SELECT t1.name
FROM table1 t1
LEFT JOIN table2 t2 ON t2.name = t1.name
WHERE t2.name IS NULL

:这里发生了什么?

A:从概念上讲,我们从table1 中选择所有行,并且对于每一行,我们尝试在table2 中找到与name 列具有相同值的行。如果没有这样的行,我们只需将该行的结果的table2 部分留空。然后,我们通过仅选择结果中不存在匹配行的那些行来限制我们的选择。最后,我们忽略结果中的所有字段,除了name 列(我们确定存在的字段,来自table1)。

虽然它可能不是在所有情况下都可能是性能最高的方法,但它应该适用于几乎所有尝试实现 ANSI 92 SQL 的数据库引擎

【讨论】:

@z-boss:它也是 SQL Server 上性能最低的:explainextended.com/2009/09/15/… @BunkerBoy:左连接允许右侧的行不存在,而不会影响左侧行的包含。内连接需要左右两边的行都存在。我在这里做的是应用一些逻辑来基本上获得内部连接的反向选择。 天哪,这有助于很容易地进行可视化,其他人已经把它说成 5 种不同的方式,但这很有帮助。简单:首先你得到左连接,A 中的所有内容,以及 B 中与 A 匹配的所有内容。但是就像在不连接的左连接字段中发生的那样,它只是空的。然后你告诉我,好吧,我只希望那是空的。这样,您现在在 A 中拥有所有在 B 中没有匹配项的行 应该指出,这个解决方案(被接受并投票赞成)是唯一的,我认为,可以针对多个字段发挥作用的场景进行编辑。具体来说,我从表一中返回字段、字段 2、字段 3,其中字段 ad field2 的组合不在第二个表中。除了修改这个答案中的连接之外,我看不到用下面争论的其他一些“更有效的答案”来做到这一点 只要确保使用“WHERE t2.name IS NULL”而不是“AND t2.name IS NULL”,因为“and”不会给出正确的结果。我真的不明白为什么,但这是事实,我测试过。【参考方案2】:

你可以这样做

SELECT name
FROM table2
WHERE name NOT IN
    (SELECT name 
     FROM table1)

SELECT name 
FROM table2 
WHERE NOT EXISTS 
    (SELECT * 
     FROM table1 
     WHERE table1.name = table2.name)

请参阅this question 了解实现此目的的 3 种技术

【讨论】:

这对于大量数据来说非常慢。 是的,确实很慢 不存在查询的子查询中不应该是“from table1”吗? 对这如何获得如此多的赞成票感到非常困惑。我发现很难想出使用它的理由,因为有一种方法可以在几乎相同的击键次数下更快地解决这个问题。 @searchengine27 我们有查询优化器真的那么慢吗?【参考方案3】:

我没有足够的代表点来投票 froadie's answer。但我不得不不同意Kris's answer 上的cmets。以下答案:

SELECT name
FROM table2
WHERE name NOT IN
    (SELECT name 
     FROM table1)

FAR 在实践中是否更有效。我不知道为什么,但是我正在针对 800k+ 记录运行它,并且由于上面发布的第二个答案的优势,差异是巨大的。只是我的 0.02 美元。

【讨论】:

在 NOT IN 查询中,子查询只执行一次,在 EXISTS 查询中,对每一行执行子查询 你太棒了 :) 这样我将使用左连接的 25 秒查询转换为仅 0.1 秒 答案没有任何特定的顺序,所以第二个答案并不代表你认为的意思。 如果您希望向子查询添加一些额外的过滤器/标准,我认为这也可能是唯一的解决方案。【参考方案4】:
SELECT <column_list>
FROM TABLEA a
LEFTJOIN TABLEB b 
ON a.Key = b.Key 
WHERE b.Key IS NULL;

https://www.cloudways.com/blog/how-to-join-two-tables-mysql/

【讨论】:

Say NO to Venn Diagrams When Explaining JOINs 太糟糕了,连接图比维恩图更不清晰,更难以直观理解。 感谢您的图表【参考方案5】:

这是纯集合论,您可以通过 minus 操作来实现。

select id, name from table1
minus
select id, name from table2

【讨论】:

你觉得这比left join效率高吗? 应该是。减号命令就是针对这种情况而设计的。当然,判断任何特定数据集的唯一方法是两种方式都尝试,看看哪个运行得更快。 在 T-SQL 中,集合运算符是“except”。这对我来说非常方便,并且没有造成任何减速。 在 SQLite 中,“减号”运算符也是“除外”。 MySQL 不支持 MINUS 运算符。【参考方案6】:

这是最适合我的方法。

SELECT *
FROM @T1
EXCEPT
SELECT a.*
FROM @T1 a
JOIN @T2 b ON a.ID = b.ID

这是我尝试过的任何其他方法的两倍多。

【讨论】:

谢谢,这也适用于大量数据!但我只是想知道“除外”一词。 在 200k 条记录中的 5k 条记录对我来说是 767 毫秒。其他一切都需要几分钟。【参考方案7】:

注意陷阱。如果Table1 中的字段Name 包含Null,您会感到惊讶。 更好的是:

SELECT name
FROM table2
WHERE name NOT IN
    (SELECT ISNULL(name ,'')
     FROM table1)

【讨论】:

COALESCE > ISNULL(ISNULL 是对语言的无用 T-SQL 补充,没有什么比 COALESCE 更好的新功能)【参考方案8】:

您可以在 mssql 中使用 EXCEPT 或在 oracle 中使用 MINUS,它们是相同的:

http://blog.sqlauthority.com/2008/08/07/sql-server-except-clause-in-sql-server-is-similar-to-minus-clause-in-oracle/

【讨论】:

【参考方案9】:

这对我很有效

SELECT * 
FROM [dbo].[table1] t1
LEFT JOIN [dbo].[table2] t2 ON t1.[t1_ID] = t2.[t2_ID]
WHERE t2.[t2_ID] IS NULL

【讨论】:

【参考方案10】:

查看查询:

SELECT * FROM Table1 WHERE
id NOT IN (SELECT 
        e.id
    FROM
        Table1 e
            INNER JOIN
        Table2 s ON e.id = s.id);

概念上是:在子查询中获取匹配的记录,然后在主查询中获取不在子查询中的记录。

【讨论】:

【参考方案11】:

首先定义表的别名,如t1t2。 之后获得第二张桌子的记录。 之后使用where 条件匹配该记录:

SELECT name FROM table2 as t2
WHERE NOT EXISTS (SELECT * FROM table1 as t1 WHERE t1.name = t2.name)

【讨论】:

你的答案是一样的that。请阅读所有答案,尤其是在回答老问题之前。 别人的专业回答复制!【参考方案12】:

上述所有查询在大表上都非常慢。需要改变策略。这是我用于我的数据库的代码,您可以音译更改字段和表名。

策略如下:创建两个隐式临时表并将它们合并。

    第一个临时表来自第一个原始表的所有行的选择,其中您要控制的字段不存在于第二个原始表中。 第二个隐式临时表包含两个原始表中与您要控制的列/字段的相同值匹配的所有行。 联合的结果是一个表,其中包含多个具有相同控制字段值的行,以防两个原始表(一个来自第一个选择,第二个来自第二个选择),并且只有一行具有控制列值,以防第一个原始表的值与第二个原始表的任何值都不匹配。 你分组和计数。当计数为 1 时,不匹配,最后只选择计数等于 1 的行。

看起来并不优雅,但比上述所有解决方案都要快几个数量级。

重要提示:启用要检查的列上的索引。

SELECT name, source, id
FROM 
(
    SELECT name, "active_ingredients" as source, active_ingredients.id as id 
        FROM active_ingredients

    UNION ALL
        
    SELECT active_ingredients.name as name, "UNII_database" as source, temp_active_ingredients_aliases.id as id 
    FROM active_ingredients
    INNER JOIN temp_active_ingredients_aliases ON temp_active_ingredients_aliases.alias_name = active_ingredients.name

) tbl
GROUP BY name
HAVING count(*) = 1
ORDER BY name

【讨论】:

【参考方案13】:

我将在正确答案中重新发布(因为我还不够酷,无法发表评论)......以防其他人认为需要更好地解释。

SELECT temp_table_1.name
FROM original_table_1 temp_table_1
LEFT JOIN original_table_2 temp_table_2 ON temp_table_2.name = temp_table_1.name
WHERE temp_table_2.name IS NULL

我已经看到 FROM 中的语法需要在 mySQL 中的表名之间使用逗号,但在 sqlLite 中它似乎更喜欢空格。

最重要的是,当您使用错误的变量名称时,它会留下问题。我的变量应该更有意义。并且应该有人解释为什么我们需要逗号或不使用逗号。

【讨论】:

【参考方案14】:

您可以使用以下查询结构:

SELECT t1.name FROM table1 t1 JOIN table2 t2 ON t2.fk_id != t1.id;

表1:

id name
1 Amit
2 Sagar

表2:

id fk_id email
1 1 amit@ma.com

输出:

name
Sagar

【讨论】:

【参考方案15】:

我尝试了上述所有解决方案,但在我的情况下不起作用。以下查询对我有用。

SELECT name FROM table_1 WHERE name NOT IN (SELECT a.name FROM table_1 AS a 
LEFT JOIN table_2 as b ON a.name = b.name WHERE ANY FURTHER CONDITION );

【讨论】:

以上是关于如何从一个表中选择另一张表中不存在的所有记录?的主要内容,如果未能解决你的问题,请参考以下文章

SQL - 如何从一个表中获取重复记录并连接到另一张表

删除一张表中与另一张表中相同的记录

MySQL:仅当不在另一张表中时才从一张表中选择电子邮件?

mysql 获取一张表中, 另一张表不存在的数据

将 UNION ALL 与另一张表一起使用时,保留一张表中的所有记录

从一个表中选择 Laravel 5.1 中另一个表中不存在的所有记录