【问题标题】:INNER JOIN ON vs WHERE clauseINNER JOIN ON vs WHERE 子句
【发布时间】:2010-11-04 08:46:40
【问题描述】:

为简单起见,假设所有相关字段均为NOT NULL

你可以这样做:

SELECT
    table1.this, table2.that, table2.somethingelse
FROM
    table1, table2
WHERE
    table1.foreignkey = table2.primarykey
    AND (some other conditions)

否则:

SELECT
    table1.this, table2.that, table2.somethingelse
FROM
    table1 INNER JOIN table2
    ON table1.foreignkey = table2.primarykey
WHERE
    (some other conditions)

这两个在MySQL 中的工作方式是否相同?

【问题讨论】:

  • @Marco: here it is
  • 如果我理解正确的话,第一个变体是 ANSI SQL-89 隐式语法,第二个变体是 ANSI SQL-92 显式连接语法。两者都将在符合 SQL 实现的情况下产生相同的结果,并且在完成良好的 SQL 实现中都将产生相同的查询计划。我个人更喜欢 SQL-89 语法,但很多人更喜欢 SQL-92 语法。
  • @Hogan 我指出了不同语法的官方名称。没有一个答案明确拼出全名,所以我决定将它们添加为 cmets。但是,我的评论没有回答实际问题,所以我将其添加为评论,而不是答案。 (投票率高的答案声称“INNER JOIN 是 ANSI 语法”和“隐式连接 ANSI 语法更旧”,因为这两种语法都是不同的 ANSI 语法,所以什么也没说。)

标签: sql mysql join inner-join


【解决方案1】:

INNER JOIN 是您应该使用的 ANSI 语法。

通常认为它更具可读性,尤其是当您连接大量表时。

当需要时,它也可以很容易地替换为OUTER JOIN

WHERE 语法更面向关系模型。

JOINed 两个表的结果是应用了过滤器的表的笛卡尔积,该过滤器仅选择与连接列匹配的那些行。

使用WHERE 语法更容易看到这一点。

就您的示例而言,在 MySQL 中(通常在 SQL 中)这两个查询是同义词。

另外,请注意 MySQL 也有一个 STRAIGHT_JOIN 子句。

使用该子句,您可以控制JOIN的顺序:在外循环中扫描哪个表,在内循环中扫描哪个表。

您无法在 MySQL 中使用WHERE 语法来控制它。

【讨论】:

  • 谢谢,Quassnoi。你的ans中有很多细节;是否可以公平地说“是的,这些查询是等效的,但是您应该使用内部联接,因为它更具可读性并且更易于修改”?
  • @allyourcode:对于 OracleSQL ServerMySQLPostgreSQL — 是的。对于其他系统,可能也是如此,但您最好检查一下。
  • FWIW,在WHERE 子句中使用逗号和连接条件也在 ANSI 标准中。
  • @Bill Karwin:JOIN 关键字直到最近才出现在专有标准中。它仅在9 版本中进入Oracle,在7.2 版本中进入PostgreSQL(均在2001 中发布)。这个关键字的出现是ANSI 标准采用的一部分,这就是为什么这个关键字通常与ANSI 相关联,尽管后者也支持逗号作为CROSS JOIN 的同义词。
  • 尽管如此,ANSI SQL-89 指定在 WHERE 子句中使用逗号和条件进行连接(如您所说,没有条件,连接相当于交叉连接)。 ANSI SQL-92 增加了JOIN 关键字和相关语法,但为了向后兼容,仍然支持逗号样式的语法。
【解决方案2】:

一旦您需要开始向查询中添加更多表,隐式连接(即您的第一个查询就是这样)变得更加混乱、难以阅读和难以维护。想象一下,在四个或五个不同的表上执行相同的查询和连接类型......这是一场噩梦。

使用显式连接(您的第二个示例)更具可读性和易于维护。

【讨论】:

  • 我完全不同意。 JOIN 语法非常冗长且难以组织。我有很多使用 WHERE 子句连接连接 5、10 甚至 15 个表的查询,它们完全可读。使用 JOIN 语法重写这样的查询会导致乱码。这只是表明这个问题没有正确的答案,它更多地取决于你对什么感到满意。
  • 诺亚,我想你在这里可能是少数。
  • 我对马特和诺亚 +1。我喜欢多样性:)。我可以看到诺亚从哪里来;内部连接不会为语言添加任何新内容,而且肯定更冗长。另一方面,它可以使你的“where”条件更短,这通常意味着它更容易阅读。
  • 我假设任何理智的 DBMS 都会将这两个查询转换为相同的执行计划;然而实际上每个 DBMS 都是不同的,唯一确定的方法是实际检查执行计划(即,您必须自己测试它)。
  • 是否像@rafidheen 在另一个答案(具有详细的 SQL 执行顺序的答案)中建议的那样,一次过滤一个联接,与完整的笛卡尔相比,减少联接操作的大小连接 3 个或更多表,并追溯应用 WHERE 过滤器?如果是这样,则表明 JOIN 提供了性能改进(以及左/右连接的优势,正如另一个答案所指出的那样)。
【解决方案3】:

它们具有不同的人类可读含义。

但是,根据查询优化器的不同,它们对机器可能具有相同的含义。

您应该始终编写可读的代码。

也就是说,如果这是一个内置关系,则使用显式连接。如果要匹配弱相关数据,请使用 where 子句。

【讨论】:

    【解决方案4】:

    隐式连接 ANSI 语法较旧,不​​太明显,不推荐使用。

    此外,关系代数允许WHERE 子句和INNER JOIN 中的谓词可互换,因此即使带有WHERE 子句的INNER JOIN 查询也可以让优化器重新排列谓词。

    我建议您以最易读的方式编写查询。

    有时这包括使INNER JOIN 相对“不完整”,并将一些标准放在WHERE 中,只是为了使过滤标准列表更易于维护。

    例如,而不是:

    SELECT *
    FROM Customers c
    INNER JOIN CustomerAccounts ca
        ON ca.CustomerID = c.CustomerID
        AND c.State = 'NY'
    INNER JOIN Accounts a
        ON ca.AccountID = a.AccountID
        AND a.Status = 1
    

    写:

    SELECT *
    FROM Customers c
    INNER JOIN CustomerAccounts ca
        ON ca.CustomerID = c.CustomerID
    INNER JOIN Accounts a
        ON ca.AccountID = a.AccountID
    WHERE c.State = 'NY'
        AND a.Status = 1
    

    但这当然取决于。

    【讨论】:

    • 你的第一个 sn-p 肯定更伤我的大脑。真的有人这样做吗?如果我遇到这样的人,我可以打他的头吗?
    • 我找到了最有意义的标准。如果我要加入时间一致的快照查找表(并且我没有强制选择有效日期的视图或 UDF),我将在加入中而不是在 WHERE 中包含生效日期,因为它更少可能会被意外删除。
    • @allyourcode:虽然在 INNER JOIN 中很少看到这种类型的连接语法,但对于 RIGHT JOIN 和 LEFT JOIN 来说却很常见——在连接谓词中指定更多细节就不需要子查询和防止您的外部联接无意中变成内部联接。 (虽然我同意对于 INNER JOIN,我几乎总是将 c.State = 'NY' 放在 WHERE 子句中)
    • @allyourcode 我肯定会这样做!我同意凯德的观点。我很好奇是否有decent reason not to
    【解决方案5】:

    其他人指出INNER JOIN 有助于提高人类的可读性,我同意这是重中之重。
    让我尝试解释一下为什么连接语法更具可读性。

    一个基本的SELECT 查询是这样的:

    SELECT stuff
    FROM tables
    WHERE conditions
    

    SELECT 子句告诉我们什么我们要返回; FROM 子句告诉我们从哪里获取它,WHERE 子句告诉我们哪些得到了。

    JOIN 是关于表的声明,它们是如何绑定在一起的(从概念上讲,实际上是一个表)。

    任何控制表的查询元素——我们从中获取内容——在语义上属于FROM 子句(当然,这就是JOIN 元素所在的位置)。将连接元素放入WHERE 子句将whichwhere-from 混为一谈,这就是首选JOIN 语法的原因。

    【讨论】:

    • 感谢您阐明为什么首选 Carl。我认为你的 ans 在其他人中是隐含的,但明确的通常更好(是的,我是 Python 粉丝)。
    • ON 和 WHERE 的语义意味着对于最后一个 OUTER JOIN 之后的 JOIN,使用哪个并不重要。尽管您将 ON 描述为 JOIN 的一部分,但它也是在笛卡尔积之后的过滤。 Both ON 和 WHERE 过滤笛卡尔积。但是必须在最后一个 OUTER JOIN之前 使用 ON 或带有 WHERE 的子选择。 (JOIN 不是“on”列对。任何两个表都可以在任何条件下 JOIN。这只是一种专门解释 JOIN ON 列相等的方法。)
    • 即使您使用 WHERE 来实现与 INNER JOIN 相同的效果,您也将在查询的 FROM 部分中提及您的两个表。所以基本上你仍然在暗示你在 FROM 子句中获取数据的位置,所以我想你不能说它必然“将 which 和 where-from 混为一谈”
    • @ArsenKhachaturyan 仅仅因为文本中使用了关键字或标识符并不意味着它是代码并且需要代码格式。这是一种可以采取任何方式的格式选择,如果在此处编辑是合理的,那么将每个帖子不断编辑为另一种格式是合理的——也就是说,这是不合理的。 (加上内联的每字代码格式可能很难阅读。)这里的段落中断也是如此——它们并不是特别清楚。与“哪个”与“那个”相同。并且编程语言的名称应该是代码格式。 PS你添加了一个错误的换行符。
    • @philipxy 正如你提到的“这并不意味着......”,但显然这并不意味着它不能用 code 关键字标记。是的,这是要做出的选择,但是很多帖子都是在不知道这一事实的情况下完成的。因此,我做出更改的决定并不是要破坏任何东西,而是让它更具可读性。如果您在格式化更改后发现任何中断,很抱歉,您显然可以恢复此类更改。
    【解决方案6】:

    我还要指出,使用旧语法更容易出错。如果使用不带 ON 子句的内连接,则会出现语法错误。如果您使用较旧的语法并忘记 where 子句中的连接条件之一,您将获得交叉连接。开发人员通常通过添加 distinct 关键字来解决此问题(而不是修复连接,因为他们仍然没有意识到连接本身已损坏),这似乎可以解决问题,但会大大减慢查询速度。

    另外,如果您在旧语法中有一个交叉连接,维护人员将如何知道您是否打算拥有一个(在某些情况下需要交叉连接)或者它是否是应该修复的意外?

    让我指出这个问题,看看为什么使用左连接时隐式语法不好。 Sybase *= to Ansi Standard with 2 different outer tables for same inner table

    另外(个人咆哮),使用显式连接的标准已有 20 多年的历史,这意味着隐式连接语法已经过时了 20 年。你会使用已经过时 20 年的语法编写应用程序代码吗?为什么要写数据库代码呢?

    【讨论】:

    • @HLGEM:虽然我完全同意显式 JOIN 更好,但在某些情况下您只需要使用旧语法。一个真实的例子:ANSI JOIN 只在 2001 年发布的 9i 版本中进入 Oracle,直到一年前(从标准发布的那一刻起 16 年)我不得不支持一堆 8i 安装,我们有发布关键更新。我不想维护两组更新,所以我们针对包括 8i 在内的所有数据库开发和测试更新,这意味着我们无法使用 ANSI JOIN。
    • +1 有趣的一点是当您指出没有 INNER JOIN 的 sintax 更容易出错时。当你说“......使用显式连接的标准是 17 岁”时,我对你的最后一句话感到困惑。那么您是否建议使用 INNER JOIN 关键字?
    • @Marco Demaio,是的,总是使用 INNER JOIN 或 JOIN(这两个是相同的)或 LEFT JOIN 或 RIGHT JOIN 或 CROSS JOIN,并且永远不要使用隐式逗号连接。
    • "为什么要写[20岁]的数据库代码?" - 我注意到使用HAVING 编写SQL,自从SQL 开始支持派生表以来,它已经“过时”了。我还注意到您不使用NATURAL JOIN,即使我认为它已经使INNER JOIN“过时”。是的,你有你的理由(这里不需要再陈述了!):我的观点是,那些喜欢使用旧语法的人也有他们的理由,并且语法的相对年龄几乎没有相关性。
    • WHERE 仍然在标准中(告诉我它不在哪里)。所以,显然没有什么过时的。此外,“而不是修复连接”向我展示了一个开发人员,他一般应该远离 DBMS,很远
    【解决方案7】:

    ANSI 连接语法肯定更便携。

    我正在升级 Microsoft SQL Server,我还要提一下,2005 SQL Server 及更高版本不支持 SQL Server 中外连接的 =* 和 *= 语法(没有兼容模式)。

    【讨论】:

    • 即使在 SQL Server 2000 中,= 和 = 也可能给出错误的结果,并且永远不应使用。
    • *==* 从来都不是 ANSI,也从来都不是一个好的表示法。这就是为什么需要 ON 的原因——对于没有子选择的 OUTER JOIN(同时添加,因此在 CROSS 和 INNER JOIN 中实际上不需要它们。)
    【解决方案8】:

    我知道你在谈论 MySQL,但无论如何: 在 Oracle 9 中,显式连接和隐式连接会生成不同的执行计划。已在 Oracle 10+ 中解决的 AFAIK:不再有这样的区别。

    【讨论】:

      【解决方案9】:

      SQL:2003 标准更改了一些优先规则,因此 JOIN 语句优先于“逗号”连接。这实际上可以根据设置方式更改查询的结果。当 MySQL 5.0.12 切换到遵守标准时,这会给某些人带来一些问题。

      因此,在您的示例中,您的查询的工作方式相同。但是,如果您添加了第三个表: SELECT ... FROM table1,table2 JOIN table3 ON ... WHERE ...

      在 MySQL 5.0.12 之前,table1 和 table2 将首先连接,然后是 table3。现在(5.0.12 及更高版本),首先连接 table2 和 table3,然后连接 table1。它并不总是改变结果,但它可以而且你甚至可能没有意识到。

      我不再使用“逗号”语法,选择您的第二个示例。无论如何,它更具可读性,JOIN 条件与 JOIN 一起,而不是分成单独的查询部分。

      【讨论】:

      • 标准 SQL 没有改变。 MySQL 是错的,现在是对的。请参阅 MySQL 手册。
      【解决方案10】:

      在 ON / WHERE 中应用条件语句

      这里我已经解释了逻辑查询的处理步骤。


      参考:Microsoft® SQL Server™ 2005 T-SQL 查询内部
      出版商:微软出版社
      发布日期:2006 年 3 月 7 日
      打印 ISBN-10:0-7356-2313-9
      打印 ISBN-13:978-0-7356-2313-2
      页数:640

      Inside Microsoft® SQL Server™ 2005 T-SQL Querying

      (8)  SELECT (9) DISTINCT (11) TOP <top_specification> <select_list>
      (1)  FROM <left_table>
      (3)       <join_type> JOIN <right_table>
      (2)       ON <join_condition>
      (4)  WHERE <where_condition>
      (5)  GROUP BY <group_by_list>
      (6)  WITH {CUBE | ROLLUP}
      (7)  HAVING <having_condition>
      (10) ORDER BY <order_by_list>
      

      SQL 与其他编程语言不同的第一个显着方面是代码的处理顺序。在大多数编程语言中,代码是按照编写的顺序处理的。在 SQL 中,第一个处理的子句是 FROM 子句,而最先出现的 SELECT 子句几乎最后被处理。

      每个步骤都会生成一个虚拟表,用作下一个步骤的输入。这些虚拟表对调用者(客户端应用程序或外部查询)不可用。只有最后一步生成的表才会返回给调用者。如果查询中没有指定某个子句,则直接跳过相应的步骤。

      逻辑查询处理阶段简述

      如果这些步骤的描述目前似乎没有多大意义,请不要太担心。这些提供作为参考。场景示例之后的部分将更详细地介绍这些步骤。

      1. FROM:在FROM子句的前两个表之间进行笛卡尔积(交叉连接),结果生成虚拟表VT1。

      2. ON:ON 过滤器应用于 VT1。只有&lt;join_condition&gt; 为TRUE 的行才会插入到VT2。

      3. OUTER (join):如果指定了 OUTER JOIN(与 CROSS JOIN 或 INNER JOIN 相对),则将保留表中的行或未找到匹配的表添加到来自 VT2 的行中作为外部行,生成 VT3。如果 FROM 子句中出现两个以上的表,则在 FROM 子句中最后一个连接的结果和下一个表之间重复执行步骤 1 到 3,直到处理完所有表。

      4. WHERE:将 WHERE 过滤器应用于 VT3。只有 &lt;where_condition&gt; 为 TRUE 的行才会插入到 VT4。

      5. GROUP BY:VT4 中的行根据 GROUP BY 子句中指定的列列表分组排列。生成VT5。

      6. 立方体 | ROLLUP:超组(组的组)从 VT5 添加到行,生成 VT6。

      7. HAVING:HAVING 过滤器应用于 VT6。只有 &lt;having_condition&gt; 为 TRUE 的组才会插入到 VT7。

      8. SELECT:处理SELECT列表,生成VT8。

      9. DISTINCT:从 VT8 中删除重复的行。 VT9 已生成。

      10. ORDER BY:VT9 中的行根据 ORDER BY 子句中指定的列列表进行排序。生成一个游标 (VC10)。

      11. TOP:从VC10的开头选择指定的行数或百分比。生成表 VT11 并返回给调用者。



      因此,(INNER JOIN) ON 将在应用 WHERE 子句之前过滤数据(此处将自行减少 VT 的数据计数)。随后的连接条件将使用过滤后的数据执行,从而提高性能。之后,只有 WHERE 条件会应用过滤条件。

      (在 ON / WHERE 中应用条件语句在少数情况下不会产生太大影响。这取决于您连接了多少表以及每个连接表中可用的行数)

      【讨论】:

      • "因此,(INNER JOIN) ON 会在应用 WHERE 子句之前过滤数据(这里会自行减少 VT 的数据数)。" 不一定。这篇文章是关于处理的逻辑顺序。当你说一个特定的实现会先做一件事再做另一件事时,你是在谈论已实现的处理顺序。允许实现进行他们喜欢的任何优化,只要结果与实现遵循逻辑顺序相同。 Joe Celko 在 Usenet 上写了很多关于此的文章。
      • @rafidheen "(INNER JOIN) ON 将过滤数据......在应用 WHERE 子句之前......这会提高性能。"好点子。 “之后只有 WHERE 条件会应用过滤条件” HAVING 子句呢?
      • @James rafidheen 的说法是错误的。请参阅手册中的“连接优化”。还有我在这个页面上的其他 cmets。 (还有 MikeSherrill'CatRecall''s。)这种“逻辑”描述描述的是结果值,而不是它的实际计算方式。而且这样的实现行为不保证不会改变。
      【解决方案11】:

      如果您经常编写动态存储过程,您会爱上您的第二个示例(使用 where)。如果您有各种输入参数和大量变形混乱,那么这是唯一的方法。否则,它们都将运行相同的查询计划,因此经典查询肯定没有明显差异。

      【讨论】:

        【解决方案12】:

        对于隐式连接我有两点(第二个例子):

        1. 告诉数据库你想要什么,而不是它应该做什么。
        2. 您可以将所有表写在一个不受连接条件影响的清晰列表中。然后你可以更容易地阅读所有提到的表格。条件都出现在 WHERE 部分,它们也都排成一行。使用 JOIN 关键字会混淆表格和条件。

        【讨论】:

        • 这不能回答问题。隐式连接也是逗号,如第一个代码块中所示,以及您的建议。你建议的代码已经在问题中了。同样,两个代码块都不是比另一个代码块或多或少的声明性或程序性。
        猜你喜欢
        • 1970-01-01
        • 2019-01-15
        • 2014-02-07
        • 1970-01-01
        • 2013-08-11
        • 1970-01-01
        • 2012-04-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多