关于sql:inner join on vs where子句

INNER JOIN ON vs WHERE clause

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

你可以做到:

1
2
3
4
5
6
7
SELECT
    table1.this, table2.that, table2.somethingelse
FROM
    table1, table2
WHERE
    table1.foreignkey = table2.primarykey
    AND (SOME other conditions)

否则:

1
2
3
4
5
6
7
SELECT
    table1.this, table2.that, table2.somethingelse
FROM
    table1 INNER JOIN table2
    ON table1.foreignkey = table2.primarykey
WHERE
    (SOME other conditions)

这两种方法在MySQL中的作用相同吗?


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

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

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

WHERE语法更面向关系模型。

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

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

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

另外,mysql还有一个STRAIGHT_JOIN子句。

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

在MySQL中,您不能使用WHERE语法来控制它。


其他人指出,内部连接有助于人类的可读性,这是一个首要任务;我同意。让我试着解释一下为什么连接语法更易读。

基本的选择查询是:

1
2
3
SELECT stuff
FROM TABLES
WHERE conditions

select子句告诉我们要返回什么;from子句告诉我们从哪里获取它,where子句告诉我们要获取哪些。

join是一个关于表的语句,它们是如何绑定在一起的(实际上,在概念上,是如何绑定到一个表中)。任何控制表的查询元素(我们从中获取数据)在语义上都属于FROM子句(当然,这就是连接元素的位置)。将联接元素放入WHERE子句会混淆WHO和WHERE FROM;这就是为什么优先使用联接语法的原因。


在on/where中应用条件语句

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

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

在微软内部?SQL Server?2005 T-SQL查询

1
2
3
4
5
6
7
8
9
(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。

  • 打开:打开过滤器应用于VT1。仅将为真的行插入到vt2。

  • 外部(联接):如果指定了外部联接(与交叉联接或内部联接相反),则将保留表中的行或找不到匹配项的表中的行作为外部行添加到vt2中的行中,生成vt3。如果FROM子句中出现两个以上的表,则在最后一个联接的结果和FROM子句中的下一个表之间重复应用步骤1到3,直到处理完所有表。

  • 其中:将where过滤器应用于vt3。仅将为真的行插入到vt4。

  • group by:根据group by子句中指定的列列表,将vt4中的行分组排列。生成VT5。

  • cube汇总:将超级组(组组组)添加到vt5的行中,生成vt6。

  • HAVING:HAVING滤波器应用于VT6。只有为真的组才插入到VT7中。

  • 选择:处理选择列表,生成vt8。

  • distinct:从vt8中删除重复的行。生成VT9。

  • ORDER BY:VT9中的行根据ORDER BY子句中指定的列列表进行排序。生成光标(VC10)。

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

  • 因此,(inner join)on将在应用where子句之前过滤数据(vt的数据计数将在这里减少)。随后的连接条件将使用过滤后的数据执行,从而提高性能。之后,只有Where条件将应用筛选条件。

    (在on/where中应用条件语句在少数情况下不会有太大的区别。这取决于已联接的表数和每个联接表中可用的行数)


    隐式join-ansi语法较旧,不太明显,不推荐使用。

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

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

    有时,这包括使INNER JOIN相对"不完整",并将一些标准放入WHERE中,以使筛选标准列表更容易维护。

    例如,而不是:

    1
    2
    3
    4
    5
    6
    7
    8
    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

    写:

    1
    2
    3
    4
    5
    6
    7
    8
    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

    当然,这要看情况而定。


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

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


    我还将指出,使用旧语法更容易出错。如果使用不带ON子句的内部联接,将出现语法错误。如果您使用旧的语法,并且忘记了WHERE子句中的一个联接条件,那么您将得到一个交叉联接。开发人员经常通过添加distinct关键字来解决这个问题(而不是修复join,因为他们仍然没有意识到join本身被破坏),这看起来可以解决问题,但会大大降低查询速度。

    另外,对于维护,如果在旧语法中有交叉连接,那么维护人员如何知道您打算使用交叉连接(在需要交叉连接的情况下)还是应该修复的事故?

    让我指出这个问题,看看如果使用左联接,隐式语法为什么不好。Sybase*=符合ANSI标准,同一内表有两个不同的外表

    另外(这里的personal rant),使用显式连接的标准已经超过20年了,这意味着隐式连接语法已经过时20年了。您会使用已经过时20年的语法编写应用程序代码吗?为什么要编写数据库代码?


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

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

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

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


    SQL:2003标准更改了一些优先规则,因此join语句优先于"逗号"联接。这实际上可以根据查询的设置方式更改查询结果。当MySQL5.0.12转向遵循标准时,这给一些人带来了一些问题。

    所以在您的示例中,您的查询也会工作相同。但是如果您添加了第三个表:选择…从表1,表2加入表3…在哪里?

    在MySQL5.0.12之前,首先将表1和表2连接起来,然后是表3。现在(5.0.12及以上),首先连接表2和表3,然后连接表1。它并不总是改变结果,但它可以,你甚至可能没有意识到。

    我不再使用"逗号"语法,选择第二个例子。无论如何,它的可读性要高得多,连接条件是与连接一起的,而不是分成单独的查询部分。


    我知道你在谈论MySQL,但无论如何:在Oracle9中,显式联接和隐式联接将生成不同的执行计划。已经在Oracle10+中解决的afaik:再也没有这样的区别了。


    ansi-join语法绝对更易于移植。

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