ChatGPT解决这个技术问题 Extra ChatGPT

INNER JOIN ON vs WHERE 子句

为简单起见,假设所有相关字段都是 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 中的工作方式是否相同?

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

M
Melanie Shebel

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

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

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

WHERE 语法更面向关系模型。

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

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

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

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

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

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


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

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

在 ON / WHERE 中应用条件语句

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

参考:Inside Microsoft® SQL Server™ 2005 T-SQL 查询出版商:Microsoft Press Pub 日期: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 子句几乎最后处理。

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

逻辑查询处理阶段的简要说明

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

FROM:在FROM子句的前两个表之间进行笛卡尔积(交叉连接),结果生成虚拟表VT1。 ON:对 VT1 应用 ON 滤波器。只有 为 TRUE 的行才会插入到 VT2。 OUTER (join):如果指定了 OUTER JOIN(与 CROSS JOIN 或 INNER JOIN 相对),则将保留表中的行或未找到匹配的表添加到 VT2 的行中作为外行,生成VT3。如果 FROM 子句中出现两个以上的表,则在最后一个连接的结果和 FROM 子句中的下一个表之间重复应用步骤 1 到 3,直到处理完所有表。 WHERE:将 WHERE 过滤器应用于 VT3。只有 为 TRUE 的行才会插入到 VT4。 GROUP BY:VT4 中的行根据 GROUP BY 子句中指定的列列表分组排列。产生VT5。立方体 | ROLLUP:将超组(组的组)添加到来自 VT5 的行,生成 VT6。 HAVING:HAVING 过滤器应用于 VT6。只有 为 TRUE 的组才会插入到 VT7。 SELECT:处理SELECT列表,生成VT8。 DISTINCT:从 VT8 中删除重复的行。产生VT9。 ORDER BY:VT9 中的行根据 ORDER BY 子句中指定的列列表进行排序。生成游标 (VC10)。 TOP:从VC10的开头选择指定的行数或百分比。表 VT11 生成并返回给调用者。

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


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

隐式连接 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

但这当然取决于。


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

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

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


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

我还要指出,使用旧语法更容易出错。如果您使用不带 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。
当您指出没有 INNER JOIN 的 sintax 更容易出错时,+1 有趣的点。当你说“......使用显式连接的标准是 17 岁”时,我对你的最后一句话感到困惑。那么您是否建议使用 INNER JOIN 关键字?
@Marco Demaio,是的,总是使用 INNER JOIN 或 JOIN (这两个是相同的)或 LEFT JOIN 或 RIGHT JOIN 或 CROSS JOIN 并且永远不要使用隐式逗号连接。
“你为什么要写[20岁]的数据库代码?” - 我注意到使用自 SQL 开始支持派生表以来已经“过时”的 HAVING 编写 SQL。我还注意到您不使用 NATURAL JOIN,即使我认为它已使 INNER JOIN“过时”。是的,你有你的理由(这里不需要再次陈述!):我的观点是,那些喜欢使用旧语法的人也有他们的理由,而且语法的相对年龄几乎没有相关性。
WHERE 仍然在标准中(告诉我它不在哪里)。所以,显然没有什么过时的。此外,“而不是修复连接”向我展示了一个应该远离 DBMS 通常远离 DBMS 的开发人员。
J
John Gietzen

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

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

您应该始终编码可读。

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


B
Brent Baisley

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 手册。
J
João Marcus

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


M
Melanie Shebel

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


M
Melanie Shebel

ANSI 连接语法肯定更便携。

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


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

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

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


这不能回答问题。隐式连接也是逗号,如第一个代码块中所示,以及您的建议。你建议的代码已经在问题中了。同样,两个代码块都不是比另一个代码块或多或少的声明性或程序性。