MySQL加入不存在的地方

时间:2009-04-15 04:45:33

标签: mysql join not-exists

我有一个连接两个表的MySQL查询

  • 投票者
  • 户 他们加入选民.household_id和household.id

    现在我需要做的是修改它,其中选民表连接到第三个名为elimination的表,沿着voter.id和elimination.voter_id,但是我想要排除选民中的任何记录在消除表中具有相应记录的表。我如何制作一个查询来做到这一点?

这是我当前的查询

SELECT `voter`.`ID`, `voter`.`Last_Name`, `voter`.`First_Name`,
       `voter`.`Middle_Name`, `voter`.`Age`, `voter`.`Sex`,
       `voter`.`Party`, `voter`.`Demo`, `voter`.`PV`,
       `household`.`Address`, `household`.`City`, `household`.`Zip`
FROM (`voter`)
JOIN `household` ON `voter`.`House_ID`=`household`.`id`
WHERE `CT` = '5'
AND `Precnum` = 'CTY3'
AND  `Last_Name`  LIKE '%Cumbee%'
AND  `First_Name`  LIKE '%John%'
ORDER BY `Last_Name` ASC
LIMIT 30 

4 个答案:

答案 0 :(得分:152)

我可能会使用一个Left Join,即使没有匹配也会返回行,然后你可以通过检查NULL来只选择没有匹配的行。

所以,比如:

SELECT V.*
FROM voter V LEFT JOIN elimination E ON V.id = E.voter_id
WHERE E.voter_id IS NULL

这比使用子查询更高效还是更低效取决于优化,索引,每个选民是否可以有多个淘汰等等。

答案 1 :(得分:4)

我会使用'不存在的地方' - 正如你在标题中所建议的那样:

SELECT `voter`.`ID`, `voter`.`Last_Name`, `voter`.`First_Name`,
       `voter`.`Middle_Name`, `voter`.`Age`, `voter`.`Sex`,
       `voter`.`Party`, `voter`.`Demo`, `voter`.`PV`,
       `household`.`Address`, `household`.`City`, `household`.`Zip`
FROM (`voter`)
JOIN `household` ON `voter`.`House_ID`=`household`.`id`
WHERE `CT` = '5'
AND `Precnum` = 'CTY3'
AND  `Last_Name`  LIKE '%Cumbee%'
AND  `First_Name`  LIKE '%John%'

AND NOT EXISTS (
  SELECT * FROM `elimination`
   WHERE `elimination`.`voter_id` = `voter`.`ID`
)

ORDER BY `Last_Name` ASC
LIMIT 30

这可能比进行左连接快得多(当然,取决于你的索引,你的表的基数等),并且几乎肯定比使用IN更快。

答案 2 :(得分:3)

有三种方法可以做到这一点。

  1.         SELECT  lt.* FROM    table_left lt
            LEFT JOIN
                table_right rt
            ON      rt.value = lt.value
            WHERE   rt.value IS NULL
    
    1.         SELECT  lt.* FROM    table_left lt
              WHERE   lt.value NOT IN
              (
              SELECT  value
              FROM    table_right rt
              )
      
      1.         SELECT  lt.* FROM    table_left lt
                WHERE   NOT EXISTS
                (
                SELECT  NULL
                FROM    table_right rt
                WHERE   rt.value = lt.value
                )
        

答案 3 :(得分:0)

作为一名 14 年以上的软件工程师,在我职业生涯的早期,我在 RDBMS 上“咬牙切齿”,并且 RDBMS 始终是我整体角色职责的一部分 - 我会做其他 DBA、DBD 和 SDE 的 SE等,如果我没有在这里提供我的 0.05 美元(根据通货膨胀进行调整),同样会造成伤害。

关于连接、子查询,包含 (IN) / 排除 (NOT IN) WHERE 子句 - 不考虑任何其他附带或上下文参数而对一个比另一个更有效/性能的概括性肯定是不明智的。

根据我多年来的经验,在通过多种不同的 RDBMS 风格(MSSQL、PostgreSQL、Oracle、MySQL)和多种不同的架构和支持基础设施(本地机架、共置机架、云托管虚拟、多种不同的混合组合) - 根据您当前的需求、您当前运行的 RDBMS 类型以及每个拥有的细微差别与它们的对比,选择一种方法而不是另一种方法可能会或可能不会更好地为您服务对应物,您的连接需要深入多少层,架构在其存储的数据方面设计得有多好,以及该数据主要由它所服务的应用程序访问的方式,执行日常操作维护的频率(例如索引碎片整理、统计更新、持续监控和删除未使用的索引等)。我们这些每天忙于工作的人也意识到随着时间的推移,需求和优先级每天都在变化,就像优先级一样,数据库性能也会发生变化。我已经看到 SQL 以一致和可靠的方式运行,多年来在不同的 RDBMS 上返回亚秒级结果集,并且通过数百万行增长开始超时,日志阻塞系统的其余部分从一天到下一个。

我的意思是,不要总是假设以前有效的方法将永远有效,永远有效。不要锁定你的大脑,即使用子查询与连接或反之亦然会毫无例外地表现得更好。掌握根据具体情况进行评估的能力,并准备好尝试每种方法来比较每种方法的执行情况 - 并了解即使在最初的分析和设计决策之后,三个月后,结果可能与众不同。

现在 - 将我的肥皂盒推到一边,让我解决最初的问题。在我个人看来,根据我的经验,我会尽量避免使用 LEFT JOIN。这并不是我说在某些情况下使用 LEFT JOIN 可能不适用,而是我说要非常警惕它们并且仅在您别无选择时才使用。尤其是涉及到任何将进入生产环境的代码。 LEFT JOIN 不应用于生产代码。最根本的原因是 - LEFT JOIN 本质上是“非确定性”语句。这意味着,从一次执行到另一次执行,相同的 SQL 语句可能会产生截然不同且通常出乎意料的结果。您永远不想成为必须在周一向 CTO 解释为什么您将代码推送到 PROD 的 DBA,其中包含一些带有 LEFT JOIN 的 SQL,这些 SQL 在发布后的周五运行良好,但现在在周末报告数据聚合后的周一早上作业运行,创建了一个笛卡尔产品,该产品已导致您的 PROD DB 堆栈的日志堵塞,阻止您的客户和内部业务用户登录,更不用说使用您的平台了,只能被标记为“SiDoS”(自我造成的拒绝服务)事件。

如果您发现自己处于这样一种情况,您觉得使用 LEFT JOIN 是唯一的选择,请向自己保证总有另一种选择。考虑使用 UNIONS、COALESCE、IFNULL / ISNULL 等 - 这样当给定条件存在数据时,您总是可以在结果集中返回一条记录,当数据不存在时 - 并以记录返回的方式使用它们否则不会返回(您的 NULL 数据...)的 INNER JOIN 很容易与合法记录区分开来。然后,在您的 where 子句中结合智能“过滤器”,可以将它们过滤掉,或者如果您能够通过下一步将它们从上游 /dev/null 过滤掉,那也可以。你总是有选择。

在这些情况下,我也会使用我个人喜欢的技术来结束。它解决了这个线程中手头的问题,但也谈到了 RDBMS 不可知的 SQL 编写......

我更喜欢首先以更“老派”的方法编写 JOIN 类型的语句,省略任何特定的 JOIN 语句等 - 让 RDBMS 查询解析器执行其设计的操作 - 分析您的语句并将其转换为基于其“幕后”编程的执行计划,其方式最适合 RDBMS 软件和您的自定义数据集。现在 - 我绝对强调(就像在任何其他情况下一样)内置于查询解析器的 RDBMS 甚至可能出错,相信我,我已经看到它一次又一次地发生 - 但是,我觉得首先采用这种方法通常会给我足够的信息,以便在大多数情况下对此处讨论的细节做出明智的后续调整决策。

所以,为了说明我的意思,以问题查询为例:

SELECT `voter`.`ID`, `voter`.`Last_Name`, `voter`.`First_Name`,
       `voter`.`Middle_Name`, `voter`.`Age`, `voter`.`Sex`,
       `voter`.`Party`, `voter`.`Demo`, `voter`.`PV`,
       `household`.`Address`, `household`.`City`, `household`.`Zip`
FROM (`voter`)
JOIN `household` ON `voter`.`House_ID`=`household`.`id`
WHERE `CT` = '5'
AND `Precnum` = 'CTY3'
AND  `Last_Name`  LIKE '%Cumbee%'
AND  `First_Name`  LIKE '%John%'

AND NOT EXISTS (
  SELECT * FROM `elimination`
   WHERE `elimination`.`voter_id` = `voter`.`ID`
)

ORDER BY `Last_Name` ASC
LIMIT 30

考虑在没有上面明确的 JOIN 和 NOT EXISTS 语句的情况下重写它,就像这样(我假设 WHERE 子句中的非完全限定字段属于投票者表):

SELECT v.`ID`, v.`Last_Name`, v.`First_Name`,
       v.`Middle_Name`, v.`Age`, v.`Sex`,
       v.`Party`, v.`Demo`, v.`PV`,
       h.`Address`, h.`City`, h.`Zip`
FROM `voter` v, `household` h, `elimination` e
WHERE v.`House_ID` = h.`id`
AND v.`ID` != e.`voter_id`
AND v.`CT` = '5'
AND v.`Precnum` = 'CTY3'
AND  v.`Last_Name`  LIKE '%Cumbee%'
AND  v.`First_Name`  LIKE '%John%'
ORDER BY v.`Last_Name` ASC
LIMIT 30;

尝试以两种方式在语法上向前编写一些未来的 SQL 查询,比较它们的结果,然后看看您的想法。以我上面建议的风格编写 SQL 带来了额外的好处,即今天大多数现代 RDBMS 都支持它,而无需为不同风格的 RDBMS 编写高度具体的查询来处理语法差异。

无论如何 - 我希望我在这里的建议可以帮助至少一位数据极客同行 - 我非常欢迎对我的意见提出任何问题或挑战。这些类型的辩论使我们在这个行业中变得更加强大:-)

干杯!