关于语法:SQL左连接与FROM行上的多个表?

SQL left join vs multiple tables on FROM line?

大多数SQL方言都接受以下两种查询:

1
2
3
4
5
6
7
SELECT a.foo, b.foo
FROM a, b
WHERE a.x = b.x

SELECT a.foo, b.foo
FROM a
LEFT JOIN b ON a.x = b.x

现在显然,当需要外部联接时,需要第二种语法。但是,在进行内部联接时,为什么我更喜欢第二种语法而不是第一种语法(反之亦然)?


在大多数现代数据库中,旧的语法(只列出表,并使用WHERE子句指定联接条件)已被弃用。

这不仅仅是为了展示,当您在同一个查询中同时使用内部联接和外部联接时,旧语法可能会模棱两可。

我给你举个例子。

假设您的系统中有3个表:

1
2
3
Company
Department
Employee

每个表包含许多行,链接在一起。你有多个公司,每个公司可以有多个部门,每个部门可以有多个员工。

好的,现在您要执行以下操作:

List all the companies, and include all their departments, and all their employees. Note that some companies don't have any departments yet, but make sure you include them as well. Make sure you only retrieve departments that have employees, but always list all companies.

所以你这样做:

1
2
3
4
SELECT * -- for simplicity
FROM Company, Department, Employee
WHERE Company.ID *= Department.CompanyID
  AND Department.ID = Employee.DepartmentID

请注意,最后一个是内部联接,以便满足您只希望部门与人员一起使用的标准。

好吧,那么现在发生了什么。问题在于,它取决于数据库引擎、查询优化器、索引和表统计信息。让我解释一下。

如果查询优化器确定这样做的方法是首先获取一个公司,然后查找部门,然后与员工进行内部联接,那么就不会得到任何没有部门的公司。

这样做的原因是,WHERE子句确定哪些行最终会出现在最终结果中,而不是行的各个部分。

在这种情况下,由于左联接,department.id列将为空,因此当涉及到内部联接到employee时,无法满足employee行的约束,因此它不会出现。

另一方面,如果查询优化器决定先处理部门员工联接,然后与公司进行左联接,您将看到它们。

所以旧的语法是模棱两可的。如果不处理查询提示,就无法指定所需的内容,而且有些数据库根本无法指定。

输入新的语法,您可以选择。

例如,如果您想要所有公司,如问题描述所述,这就是您要写的:

1
2
3
4
5
SELECT *
FROM Company
     LEFT JOIN (
         Department INNER JOIN Employee ON Department.ID = Employee.DepartmentID
     ) ON Company.ID = Department.CompanyID

在这里,您指定希望将部门员工加入作为一个加入来完成,然后让其与公司一起加入结果。

另外,假设您只需要在其名称中包含字母X的部门。同样,使用旧样式的联接,如果公司没有名称中带有x的任何部门,但使用新语法,您也可能会失去公司,可以这样做:

1
2
3
4
5
SELECT *
FROM Company
     LEFT JOIN (
         Department INNER JOIN Employee ON Department.ID = Employee.DepartmentID
     ) ON Company.ID = Department.CompanyID AND Department.Name LIKE '%X%'

这个额外的子句用于联接,但不是整行的筛选器。因此,该行可能与公司信息一起出现,但可能在该行的所有"部门"和"员工"列中都有空值,因为该公司没有名称为x的部门。旧语法很难做到这一点。

这就是为什么,除其他供应商外,自SQL Server 2005及更高版本以来,Microsoft一直反对使用旧的外部联接语法,而不是旧的内部联接语法。使用旧式外部联接语法与Microsoft SQL Server 2005或2008上运行的数据库进行对话的唯一方法是将该数据库设置为8.0兼容模式(即SQL Server 2000)。

另外,通过向查询优化器抛出一堆表和一堆WHERE子句,旧方法类似于说"在这里,尽你所能"。使用新的语法,查询优化器需要做的工作更少,以便找出哪些部分组合在一起。

所以你有了它。

左边和里面的连接是未来的浪潮。


联接语法将条件保持在它们应用于的表附近。这在联接大量表时特别有用。

顺便说一下,您也可以使用第一个语法进行外部联接:

1
WHERE a.x = b.x(+)

1
WHERE a.x *= b.x

1
WHERE a.x = b.x OR a.x NOT IN (SELECT x FROM b)


第一种方法是旧标准。第二种方法在SQL-92中介绍,http://en.wikipedia.org/wiki/sql。完整的标准可以在http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt上查看。

数据库公司花了很多年才采用SQL-92标准。

因此,第二种方法被首选的原因是,根据ANSI和ISO标准委员会的SQL标准。


基本上,当FROM子句列出这样的表时:

1
2
SELECT * FROM
  tableA, tableB, tableC

结果是表A、B、C中所有行的叉积,然后应用限制WHERE tableA.id = tableB.a_id,这将丢弃大量行,然后进一步……AND tableB.id = tableC.b_id然后你应该只得到那些你真正感兴趣的行。

DBMS知道如何优化此SQL,以便使用联接编写此SQL的性能差异可以忽略(如果有)。使用join表示法使SQL语句更具可读性(imho,不使用join会使语句变得一团糟)。使用叉积,您需要在WHERE子句中提供联接条件,这就是符号的问题。你把WHERE子句塞满了

1
2
    tableA.id = tableB.a_id
AND tableB.id = tableC.b_id

它只用于限制叉积。WHERE子句应该只包含对结果集的限制。如果将表联接条件与结果集限制混合使用,您(和其他人)会发现查询更难读取。您必须使用join并保留from子句A from子句和where子句A where子句。


第二种是首选的,因为它不太可能由于忘记放在WHERE子句中而导致意外的交叉连接。不带ON子句的联接将无法通过语法检查,不带WHERE子句的旧式联接将不会失败,它将执行交叉联接。

另外,当您稍后必须进行左联接时,它们都处于相同的结构中,这对维护很有帮助。旧语法从1992年就已经过时了,现在已经不是停止使用它的时候了。

另外,我发现许多专门使用第一种语法的人并不真正理解连接,理解连接对于在查询时获得正确的结果至关重要。


我认为在本页上采用第二种方法有一些很好的理由——使用显式连接。然而,关键是当连接条件从WHERE子句中删除时,在WHERE子句中看到剩余的选择条件会变得更加容易。

在非常复杂的select语句中,读者更容易理解正在发生的事情。


对于一些表来说,SELECT * FROM table1, table2, ...语法是可以的,但随着表的数量增加,它变得越来越难读取(不一定是数学上精确的语句)。

连接语法(在开始时)很难编写,但它明确了什么条件影响哪些表。这使得犯错误更加困难。

另外,如果所有连接都是内部的,那么两个版本都是等效的。然而,当您在语句中的任何地方有一个外部联接时,事情就会变得更加复杂,并且实际上可以保证您所写的内容不会查询您认为您所写的内容。


当需要外部联接时,并不总是需要第二种语法:

神谕:

1
2
3
SELECT a.foo, b.foo
  FROM a, b
 WHERE a.x = b.x(+)

mssqlserver(尽管在2000版本中已弃用)/sybase:

1
2
3
SELECT a.foo, b.foo
  FROM a, b
 WHERE a.x *= b.x

但回到你的问题上来。我不知道答案,但这可能与这样一个事实有关:当你做的时候,连接比在WHERE子句中添加一个表达式更自然(至少在语法上是这样):连接。


第一个和第二个查询可能产生不同的结果,因为左联接包括第一个表中的所有记录,即使右表中没有相应的记录。


在数据库中,它们最终是相同的。不过,对于您来说,在某些情况下必须使用第二种语法。为了编辑最终不得不使用它的查询(发现您需要一个左连接,而您需要一个直连接),为了一致性,我只在第二个方法上进行模式化。这将使阅读查询更容易。


我听到很多人抱怨第一个太难理解,而且还不清楚。我看不出它有什么问题,但是在讨论之后,为了清晰起见,我甚至在内部连接中使用了第二个问题。