【问题标题】:Why use a JOIN clause versus a WHERE condition?为什么使用 JOIN 子句而不是 WHERE 条件?
【发布时间】:2010-12-09 10:57:38
【问题描述】:

我针对 Oracle 数据库进行开发。当我需要手动编写时(不使用像 hibernate 这样的 ORM),我使用 WHERE 条件而不是 JOIN。

例如(这只是为了说明风格而简单化):

Select *
from customers c, invoices i, shipment_info si
where c.customer_id = i.customer_id
    and i.amount > 999.99 
    and i.invoice_id = si.invoice_id(+)  -- added to show a replacement for a join
order by i.amount, c.name

我从一个老的 oracle DBA 那里学到了这种风格。从那以后,我了解到这不是标准的 SQL 语法。除了不标准和数据库可移植性差之外,使用这种格式还有其他影响吗?

【问题讨论】:

    标签: sql oracle


    【解决方案1】:

    旧式连接在某些情况下是完全错误的(外部连接是罪魁祸首)。尽管在使用内连接时它们或多或少是等价的,但外连接可能会产生不正确的结果,尤其是在外侧的列可以为空的情况下。这是因为当使用较旧的语法时,在构造整个结果集之前不会在逻辑上评估连接条件,根本不可能从连接外侧的列上表达一个条件:当列可以为空时将过滤记录,因为没有匹配的记录。

    举个例子:

    选择 所有 客户,以及在 8 月份处理发票的所有发票上的小部件销售额总和(Invoice.ProcessDate 为空)

    使用新的 ANSI-92 连接语法

     Select c.name, Sum(d.Amount)
     From customer c
        Left Join Invoice I 
            On i.custId = c.custId
                And i.SalesDate Between '8/1/2009' 
                          and '8/31/2009 23:59:59'
                And i.ProcessDate Is Not Null
        Left Join InvoiceDetails d 
            On d.InvoiceId = i.InvoiceId
                And d.Product = 'widget'
     Group By c.Name
    

    尝试使用旧语法执行此操作...因为当使用旧样式语法时,where 子句中的所有条件都在重新添加“外部”行之前评估/应用,所有未处理的发票行都将被添加回到最终结果集...所以这对于旧语法是不可能的 - 任何试图过滤出具有空处理日期的发票的任何事情都会消除客户......唯一的选择是使用相关子查询。

    【讨论】:

    • 旧语法不会错误地评估 OUTER JOIN。它根本无法表达 OUTER JOIN 的想法。缺少功能与错误地实现功能不同。
    • Oracle 和 SQL Server 都有表达外连接的语法,Oracle 使用 '"+="' 或 '"=+"' 而 SQL Server 过去使用 '"="' 或'"="' 分别表示左外连接和右外连接...
    • @Charles,你能举个例子说明旧的连接方式是完全错误的吗?
    • 不,有几种情况他们是错误的。我将在我的答案中添加示例...
    • from SQL Server BOL(2000) "在早期版本的 Microsoft® SQL Server™ 2000 中,左和右外连接条件是在 WHERE 子句中使用 = 和 =指定的> 运算符。在某些情况下,这种语法会导致不明确的查询,可以用多种方式解释。”他们自己说以这种方式指定的外部连接不能正常工作。旧的外连接语法已被弃用。为什么我们还在争论使用已经过时 17 年的语法?
    【解决方案2】:

    我后来了解到这不是标准的 SQL 语法。

    这并不完全正确。 "a,b where" 语法来自 ansi-89 标准,"a join b on" 语法是 ansi-92。但是,89 语法已弃用,这意味着您不应该将其用于新查询。

    此外,在某些情况下,旧样式缺乏表达能力,尤其是在外连接或复杂查询方面。

    通过 where 子句尝试挑选连接条件可能会很痛苦。对于任何多于一个的东西,旧风格都是绝对的邪恶。一旦你知道了新的风格,你也可以继续使用它。

    【讨论】:

    • 完全同意——尤其是当您的连接子句比简单的等式更复杂时。
    【解决方案3】:

    这真的取决于习惯,但我一直发现 Oracle 的逗号分隔语法更自然。第一个原因是我认为使用 (INNER) JOIN 会降低可读性。第二是关于灵活性。最后,根据定义,连接是笛卡尔积。您不必根据两个表的 ID 来限制结果。虽然很少见,但很可能需要两张表的笛卡尔积。根据 ID 限制它们只是一种非常合理的做法,但不是规则。但是,如果您使用 JOIN 关键字,例如SQL Server,它不会让你省略 ON 关键字。假设您要创建一个组合列表。你必须这样做:

    SELECT *
    FROM numbers
    JOIN letters
    ON 1=1
    

    除此之外,我觉得Oracle的(+)语法也很合理。这是一个很好的说法,“也将这条记录添加到结果集中,即使它是空的。”它比 RIGHT/LEFT JOIN 语法要好得多,因为其实没有左右之分!当你想用几种不同类型的外连接连接 10 个表时,会混淆哪个表在“左侧”,哪个在右侧。

    顺便说一句,作为更一般的评论,我认为 SQL 可移植性在实际世界中不再存在。标准的 SQL 太差了,而且经常需要各种 DBMS 特定语法的表达能力,我不认为 100% 可移植的 SQL 代码是一个可以实现的目标。我观察的最明显证据是旧的行号有问题。只需在任何论坛中搜索“sql 行号”,包括 SO,您就会看到数百篇询问如何在特定 DBMS 中实现它的帖子。与此类似并与之相关,例如,限制返回的行数也是如此。

    【讨论】:

    • SQL Server 允许您执行 CROSS JOIN 语句。这些不需要以下 ON 子句。
    • -1:错误太多。连接通过将连接条件与结果集谓词分开来提高可读性。连接允许所有类型,包括不需要 On 子句的交叉连接。 Oracle 外连接语法确实有左/右,(+) 语法需要 += 或 =+,这同样难以解释空值可以在“哪一侧”。 Left Right 一点也不难,Left 是你已经拥有的东西 Right 是你正在加入(添加)到结果集中的新表,就像你从左到右阅读一样。在某些情况下,旧的 += 语法完全是错误的。
    【解决方案4】:

    只要你不使用 ANSI 自然连接功能,我就可以。

    我发现了这句话 – ScottCher,我完全同意:

    我发现 WHERE 语法比 INNER JOIN 更容易阅读 - 我猜它就像 Vegemite。世界上大多数人可能觉得它很恶心,但长大后吃它的孩子们喜欢它。

    【讨论】:

    • vegemite 评论 +1 :-) 虽然我不同意你的技术观点:你对自然连接有什么问题?
    • 您应该具体说明您加入的列。自然连接容易出错。例如(来自ask tom site)考虑这样一个模型:create table emp (empno, deptno, ename); create table dept (deptno, dname, empno /* 该部门负责人 */ );现在,使用自然连接生成一个名称列表和它们使用的 dname。
    • 好吧,我的错。我天真地认为自然连接是基于外键的,并且只是添加了这些连接,而不是您可以通过列名猜到的所有连接。你描述的行为确实很糟糕。
    • 即使它使用 FK 也不知道该选择哪个。我有一个从 emp.deptno 到 dept.deptno 的 fk 和一个从 dept.empno 到 emp.empno 的 fk。想想当您添加或重命名列时会发生什么。呸
    【解决方案5】:

    这是一个标准的 SQL 语法,只是一个比 JOIN 更旧的标准。语法已经发展是有原因的,您应该使用更新的 JOIN 语法,因为:

    1. 更具表现力,清楚地指出了哪些表被 JOIN,JOIN 顺序,哪些条件适用于哪些 JOIN,并将过滤 WHERE 条件与 JOIN 条件分开。

    2. 它支持 LEFT、RIGHT 和 FULL OUTER JOIN,而 WHERE 语法不支持。

    我认为您不会发现 WHERE 类型的 JOIN 的可移植性远低于 JOIN 语法。

    【讨论】:

      【解决方案6】:

      我不喜欢这种风格,因为它使确定哪些WHERE 子句用于模拟JOINs 以及哪些子句用于实际过滤器变得更加困难,而且我不喜欢使确定变得不必要的困难的代码程序员的初衷。

      【讨论】:

      • +1 表示“我不喜欢让确定程序员的初衷变得不必要的困难的代码”
      • +1 恕我直言,这就是我们应该在 FROM 子句中使用 JOIN 的原因。将join与过滤条件分开时更清楚。
      【解决方案7】:

      我在使用这种格式时遇到的最大问题是倾向于忘记某些连接的 WHERE 子句,从而导致笛卡尔积。在向查询中添加新表时,这尤其常见(至少对我而言)。例如,假设有一张ADDRESSES 的桌子被扔进了混合物中,而你的大脑有点健忘:

      SELECT *
        FROM customers c, invoices i, addresses a
       WHERE c.customer_id = i.customer_id
         AND i.amount > 999.99
       ORDER BY i.amount, c.name
      

      轰隆隆!笛卡尔积! :)

      【讨论】:

      • 这是对的,但就像所有(错误最多的)语法错误一样,您在尝试运行时不会看到问题,然后修复它吗?这就是我在这件事上的经验。
      • @Jay:确实如此。当你在低资源的 Oracle 服务器上启动一个大的、失控的查询时,我仍然觉得很烦人。 :)
      • Jay,遗憾的是,大多数人都不会看到这个问题,他们只是添加一个独特的,然后继续他们的快乐方式。
      • 通过省略 JOIN 条件,也允许使用 JOIN 语法的笛卡尔积。这在 JOIN 语法中更为明显,但仍然很有可能。而且,@HLGEM,将 DISTINCT 添加到 SELECT 不会解决笛卡尔生产的问题,尤其是在从每个参与表返回主键的常见情况下。
      • @Larry:有趣,我什至不知道你可以省略加入条件!
      【解决方案8】:

      这是 Transact SQL 语法,我不太确定它有多“不可移植” - 例如,它是 Sybase 中使用的主要语法(Sybase 也支持 ANSI 语法)以及许多其他数据库(如果不是全部)。

      ANSI 语法的主要好处是它允许您编写一些 T-SQL 禁止的相当棘手的链式连接

      【讨论】:

        【解决方案9】:

        实际上,这种语法比 JOIN 更可移植,因为它几乎适用于任何数据库,但并不是每个人都支持 JOIN 语法(例如,Oracle Lite 不支持 [除非这最近发生了变化])。

        【讨论】:

        • 旧的连接语法在许多需要 outer 连接逻辑的情况下是错误的。
        【解决方案10】:

        有些人会说这种风格可读性较差,但这是习惯问题。从性能的角度来看,这并不重要,因为查询优化器会处理这个问题。

        【讨论】:

        • 除了对于某些外连接查询,旧的语法 cab 很容易给出不正确的结果,因此应该避免。
        • Charles:同意,使用 (+) 语法的外连接是 PITA。难以理解,容易出错...
        • 另一方面,ANSI 语法有更多错误,也给出了不正确的结果。我不记得 oracle join synatx 这样做的情况......你有一个例子吗?
        • 我发现情况正好相反。将查询拆分为列、表、连接和其他限制因素更具可读性。我发现 (+) 语法比 ANSI 更容易使用。我猜是个人喜好。
        • Oracle 以外的公司使用 (+) 语法?
        猜你喜欢
        • 2012-06-28
        • 1970-01-01
        • 1970-01-01
        • 2017-11-10
        • 1970-01-01
        • 2011-01-15
        • 2013-07-25
        • 2014-02-07
        • 1970-01-01
        相关资源
        最近更新 更多