INNER JOIN ON与WHERE子句


941

为简单起见,假设所有相关字段均为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是否相同?




18
如果我理解正确,则第一个变量是ANSI SQL-89隐式语法,第二个变量是ANSI SQL-92显式联接语法。两者在遵循SQL的实现中将产生相同的结果,并且在完成良好的SQL实施中都将产生相同的查询计划。我个人更喜欢SQL-89语法,但是许多人更喜欢SQL-92语法。
Mikko Rantalainen '16

11
@Hogan我指出了不同语法的正式名称。没有一个答案明确地列出了全名,因此我决定将其添加为注释。但是,我的评论未回答实际问题,因此我将其添加为评论,而非答案。(高投票答案有“ INNER JOIN是ANSI语法”和“隐式连接ANSI语法较旧”之类的说法,因为两种语法都是不同的ANSI语法,所以什么也没说。)
Mikko Rantalainen

Answers:


710

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

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

OUTER JOIN只要有需要,也可以轻松地将其替换为。

WHERE语法更关系模型为主。

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

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

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

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

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

您无法使用WHERE语法在MySQL中控制此功能。


10
谢谢,Quassnoi。您的ans中有很多细节;是否可以肯定地说“是的,这些查询是等效的,但您应该使用内部联接,因为它更易读,更易于修改”?
allyourcode 2009年

8
@allyourcode:对OracleSQL ServerMySQLPostgreSQL-是的。对于其他系统,可能也是如此,但是您最好检查一下。
Quassnoi

13
FWIW在WHERE子句中使用带有连接条件的逗号也是ANSI标准。
Bill Karwin 2010年

1
@Bill KarwinJOIN关键字直到看起来似乎是最近才成为专有标准的一部分。它Oracle仅进入版本9PostgreSQL版本7.2(均在中发布2001)。这个关键字的出现是ANSI标准采用的一部分,这就是为什么该关键字通常与关联的原因ANSI,尽管后者也支持逗号作为同义词CROSS JOIN
Quassnoi 2010年

9
尽管如此,ANSI SQL-89指定的联接必须在WHERE子句中用逗号和条件完成(如您所说,没有条件,联接等效于交叉联接)。ANSI SQL-92添加了JOIN关键字和相关语法,但仍支持逗号样式语法以实现向后兼容性。
Bill Karwin 2010年

182

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

基本的 SELECT查询是这样的:

SELECT stuff
FROM tables
WHERE conditions

SELECT子句告诉我们我们将得到什么;该FROM子句告诉我们我们从哪里得到的,以及WHERE从句告诉我们,我们得到的。

JOIN 是有关表的声明,说明如何将它们绑定在一起(实际上,实际上是绑定到单个表中)。

控制表的任何查询元素(从中获取信息的内容)在语义上都属于该FROM子句(当然,JOIN元素就在该子句中)。将joining-elements放入WHERE子句中会使“ where”和“ where-from ” 变得更平整,就是首选语法的原因。JOIN


7
感谢您澄清为什么首选内部联接Carl。我认为您的ans在其他人中是隐性的,但是显性通常会更好(是的,我是Python迷)。
allyourcode

2
ON和WHERE的语义意味着,对于最后一个OUTER JOIN之后的JOIN 使用哪个都无关紧要。尽管您将ON的特征描述为JOIN的一部分,但它也是笛卡尔积之后的过滤。这两种 ON和WHERE过滤笛卡尔乘积。但是必须在最后一个OUTER JOIN 之前使用ON或带有WHERE的子选择。(JOIN并不是“在”列对上。任何两个表都可以在任何条件下进行JOIN。这只是一种专门解释JOIN在列相等性上的方法。)
philipxy 2015年

即使使用WHERE达到INNER JOIN的相同效果,您也要在查询的FROM部分中提及两个表。因此,基本上,您仍然在FROM子句中暗示要从何处获取数据,因此我想您不能说它必然“将which and the where-from混合在一起”
cybergeek654

@ArsenKhachaturyan仅仅是因为在文本中使用了关键字或标识符,并不意味着它是代码且需要代码格式。这是一种格式选择,可以任意选择;如果在此处进行合理的编辑,则可以合理地将每篇文章不断编辑为其他格式,也就是说,这是没有道理的。(加上内嵌的每个单词的代码格式可能很难阅读。)此处的段中断点相同-并没有特别说明。与“哪个”与“那个”相同。和编程语言的名称应该不是在代码格式。PS您添加了换行错误。
philipxy

正如您提到的@philipxy,“这并不意味着...”,但显然这都不意味着它不能用code关键字标记。是的,它是一种选择,但是很多帖子都是在不知道该事实的情况下完成的。因此,我做出更改的决定并不是要破坏任何东西,而是使其更具可读性。如果在格式化更改后发现任何中断,对此表示抱歉,您显然可以还原此类更改。
Arsen Khachaturyan

143

在ON / WHERE中应用条件语句

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


参考:InsideMicrosoft®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

Microsoft®SQL Server™2005 T-SQL内部查询

(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。仅将<join_condition>TRUE所在的行插入VT2。

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

  4. WHERE:WHERE过滤器应用于VT3。仅将<where_condition>TRUE所在的行插入VT4。

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

  6. 多维数据集| ROLLUP:将超组(组的组)添加到VT5的行中,从而生成VT6。

  7. HAVING:HAVING过滤器应用于VT6。只有其<having_condition>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中应用条件语句不会有太大区别。这取决于您已联接的表数量以及每个联接表中可用的行数)


10
“因此,在应用WHERE子句之前,(INNER JOIN)ON将过滤数据(VT的数据计数本身将在此处减少)。” 不必要。本文是关于处理的逻辑顺序的。当您说某个特定的实现先做一件事又在做另一件事时,您在说的是处理的实现顺序。只要结果与实现遵循逻辑顺序相同,就可以允许实现进行自己喜欢的任何优化。Joe Celko在Usenet上写了很多有关此的内容。
Mike Sherrill'Cat

@rafidheen“((INNER JOIN)ON将在应用WHERE子句之前过滤数据...从而提高性能。” 好点子。“在那之后,只有WHERE条件将应用过滤条件”关于HAVING子句呢?
詹姆斯

@詹姆斯rafidheen的说法是错误的。请参见手册中的“加入优化”。还有我在此页面上的其他评论。(以及MikeSherrill'CatRecall's。)这种“逻辑”描述描述的是结果值,而不是实际计算结果的方式。并且这种实现行为不能保证不会改变。
philipxy

66

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

此外,关系代数还允许WHERE子句和中的谓词互换INNER JOIN,因此即使INNER JOIN使用WHERE子句的也可以使谓词由优化程序重新排列。

我建议您以最可行的方式编写查询。

有时,这包括使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

但这当然取决于。


16
您的第一个片段肯定会伤害我的大脑。有人真的这样做吗?如果我遇到这样做的人,我可以打败他吗?
allyourcode

3
我找到最有意义的标准。如果我要连接到时间上一致的快照查找表(并且我没有强制选择有效日期的视图或UDF),我将在联接中而不是在WHERE中包括生效日期,因为它要少一些可能会被意外移除。
Cade Roux

14
@allyourcode:尽管很少在INNER JOIN中看到这种类型的联接语法,但对于RIGHT JOIN和LEFT JOINS来说却很常见-在联接谓词中指定更多详细信息消除了对子查询的需求,并防止了外部联接被无意间打开进入内部联接。(尽管我同意,对于INNER JOIN,我几乎总是在WHERE子句中输入c.State ='NY')
Dave Markle

1
@allyourcode我一定会这样做!我同意凯德的看法。.我很好奇是否有一个不这样
Arth 2014年

31

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

使用显式联接(第二个示例)更加易读且易于维护。


48
我完全同意。JOIN语法非常罗y,难以组织。我有很多查询使用WHERE子句联接来连接5、10,甚至15个表,它们完全可读。使用JOIN语法重写这样的查询会导致乱码。只是表明这个问题没有正确的答案,而更多地取决于您的适应程度。
诺亚·纳特

33
诺亚,我想您可能在这里是少数。
马特b

2
我对哑光和诺亚得+1。我喜欢多样性:)。我可以看到诺亚来自哪里;内部联接不会给语言添加任何新内容,并且绝对更加冗长。另一方面,它可以使“ where”条件更短,这通常意味着它更容易阅读。
allyourcode

5
我假设任何理智的DBMS都会将两个查询转换为相同的执行计划。但是实际上每个DBMS都是不同的,唯一可以确定的方法是实际检查执行计划(即,您必须自己对其进行测试)。
马特b

就像@rafidheen在另一个答案(具有SQL执行的详细顺序的答案)中所建议的那样,一次对JOIN进行了一次过滤,与3个或更多表的完整笛卡尔连接相比,减小了连接操作的大小,这是真的吗?追溯应用WHERE过滤器?如果是这样,则表明JOIN可以提高性能(以及在左/右联接中的优势,这也在另一个答案中指出)。
詹姆斯

26

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

此外,对于维护而言,如果您使用旧语法进行交叉联接,那么维护者将如何知道您是否打算进行交叉联接(在某些情况下需要交叉联接)还是应该解决的意外?

让我指出这个问题,以了解为什么使用左联接时隐式语法不好。 Sybase * =到Ansi Standard,具有2个不同的外部表用于同一内部表

加号(此处是个人称呼),使用显式联接的标准已有20多年的历史了,这意味着隐式联接语法在这20年中已过时。您是否会使用已经使用20年的语法编写应用程序代码?为什么要编写数据库代码呢?


3
@HLGEM:虽然我完全同意显式JOIN更好,但在某些情况下,您只需要使用旧语法即可。一个真实的例子:ANSI JOIN仅在2001年发布的9i版本中才加入Oracle,直到一年前(从发布该标准的那16年),我必须支持大量的8i安装发布关键更新。我不想维护两组更新,因此我们针对包括8i在内的所有数据库开发并测试了更新,这意味着我们无法使用ANSI JOIN。
Quassnoi

当您指出不使用INNER JOIN的sintax更容易出错时,就会得到+1有趣的观点。当您说“ ...使用显式连接的标准已经17岁了”时,我对您的最后一句话感到困惑。那么您是否建议使用INNER JOIN关键字?
Marco Demaio 2011年

1
@Marco Demaio,是的,请始终使用INNER JOIN或JOIN(这两个相同)或LEFT JOIN或RIGHT JOIN或CROSS JOIN,并且永远不要使用隐式逗号联接。
HLGEM 2011年

2
“为什么要编写[已有20年历史]的数据库代码?” -我注意到编写的SQL HAVING自SQL开始支持派生表以来就已经过时了。我还注意到,NATURAL JOIN即使我认为它已经INNER JOIN“过时”,您也不会使用它。是的,您有自己的理由(无需在这里再次说明!):我的意思是,那些喜欢使用旧语法的人也有其理由,并且语法的相对年龄几乎没有关联。
一天,2016年

1
WHERE仍在标准中(向我显示哪些地方没有)。因此,显然没有什么过时的。此外,“而不是固定联接”显示我是谁应该从数据库管理系统一般保持一定距离,开发商了。
于尔根A.艾哈德

12

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

但是,取决于查询优化器,它们对计算机可能具有相同的含义。

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

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


11

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手册。
philipxy

4

我知道您在谈论MySQL,但是无论如何:在Oracle 9中,显式联接和隐式联接会生成不同的执行计划。Oracle 10+已解决的AFAIK:不再存在这种差异。


1

ANSI连接语法绝对更可移植。

我正在对Microsoft SQL Server进行升级,我还要提到2005 SQL Server和更高版本不支持SQL Server中外部联接的= *和* =语法(无兼容模式)。


2
即使在SQL Server 2000中,=和=也可能会给出错误的结果,因此切勿使用。
HLGEM,2009年

2
*==*从来没有ANSI和从来没有一个很好的注释。这就是为什么需要ON的原因-对于没有子选择的OUTER JOIN(它是同时添加的,因此CROSS&INNER JOIN中实际上并不需要它们。)
philipxy 2015年

1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.