SQL左联接与FROM行上的多个表?


256

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

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

现在显然当您需要外部联接时,需要第二种语法。但是,在进行内部联接时,为什么我应该首选第二种语法(反之亦然)?


1
古法:你是怎么找到的?尽管我的问题比“我怎么做”更是最佳实践
jmucchiello

由于这是最佳做法,因此请使其成为Wiki。
比诺伊·安东尼

1
我认为没有人对这两者的性能发表评论。任何人都可以确认或引用关于任何重大差异的合理理由吗?
ahnbizcad

@ahnbizcad给定的两个查询没有做相同的事情。第一个返回与INNER JOIN ON相同的结果。实现是特定于DBMS版本的,即使这样,也无法保证。但是,用逗号与INNER JOIN ON / WHERE与CROSS JOIN WHERE等效的情况进行DBMS转换是微不足道的。了解有关关系数据库查询的优化/实现。
philipxy

有资源推荐?我尝试从这里学习的原因是庞大而密集的手册。
ahnbizcad '17

Answers:


319

WHERE在大多数现代数据库中,不赞成使用仅列出表并使用子句指定连接条件的旧语法。

这不仅是为了展示,当您在同一查询中同时使用INNER和OUTER联接时,旧语法可能会产生歧义。

让我举一个例子。

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

Company
Department
Employee

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

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

列出所有公司,包括其所有部门和所有员工。请注意,有些公司还没有任何部门,但请确保也将其包括在内。确保仅检索有员工的部门,但始终列出所有公司。

所以你这样做:

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

请注意,最后一个有一个内部联接,以便满足您只希望部门中有人的条件。

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

如果查询优化器确定执行此操作的方法是先成立公司,然后找到部门,然后与员工进行内部联接,那么您将没有任何没有部门的公司。

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

在这种情况下,由于左联接,Department.ID列将为NULL,因此,当涉及到Employee的INNER JOIN时,就无法满足Employee行的约束,因此不会出现。

另一方面,如果查询优化器决定先解决部门雇员的合并问题,然后再与公司进行左合并,您将看到它们。

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

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

例如,如果您想要所有公司,如问题描述中所述,您将这样写:

SELECT *
FROM Company
     LEFT JOIN (
         Department INNER JOIN Employee ON Department.ID = Employee.DepartmentID
     ) ON Company.ID = Department.CompanyID

在这里,您指定希望将部门雇员加入作为一次加入,然后将加入的结果留给公司。

另外,假设您只希望名称中包含字母X的部门。同样,如果使用旧样式的联接,您也可能会失去公司,如果该公司没有名称带有X的任何部门,但是使用新语法,则可以执行以下操作:

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

这个额外的子句用于连接,但不是整个行的过滤器。因此,该行可能与公司信息一起出现,但该行的所有部门和雇员列中都可能为NULL,因为该公司的名称中没有任何部门带有X。使用旧语法很难做到这一点。

这就是为什么在其他供应商中,自SQL Server 2005及更高版本以来,Microsoft已弃用旧的外部联接语法,而不弃用旧的内部联接语法。使用旧风格的外部联接语法与运行在Microsoft SQL Server 2005或2008上的数据库进行通讯的唯一方法是将该数据库设置为8.0兼容模式(又名SQL Server 2000)。

另外,通过在查询优化器中抛出一堆表以及一堆WHERE子句的旧方法类似于说“在这里,尽力而为”。使用新的语法,查询优化器只需很少的工作就可以确定哪些部分组合在一起。

所以你有它。

LEFT和INNER JOIN是未来的潮流。


28
“在大多数现代数据库中已被弃用。” ---只是好奇,哪个?
zerkms 2011年

10
原谅我,我对* =运算符不熟悉,它有什么作用?谢谢!
ultrajohn

9
Star =和= Star是(很明显)左右外部连接,还是左右?被弃用了好半天,我没有用他们,因为SQL Server的6
托尼·霍普金森

3
不建议使用逗号。决不标准OUTER JOIN语法*=/ =*/ *=*弃用。
2015年

1
这个答案甚至没有回答问题,这与外部联接无关。关于逗号与INNER JOIN ON确实提出的一个说法(重新优化)是错误的。
philipxy

17

JOIN语法将条件保持在适用于其的表附近。当您连接大量表时,这特别有用。

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

WHERE a.x = b.x(+)

要么

WHERE a.x *= b.x

要么

WHERE a.x = b.x or a.x not in (select x from b)

2
* =语法在MS SQLServer中已被弃用,这是有充分的理由的:它不仅使阅读变得更困难,而且并没有像人们认为的那样起作用,并且与外观类似的LEFT JOIN不同。(+)语法对我来说并不熟悉;那是什么SQL实现?
Euro Micelli

2
至少Oracle使用了其他语法。
Lasse V. Karlsen,

4
永远不要使用SQL Server语法* =,它不会给出一致的结果,因为它有时会解释为交叉联接而不是左联接。即使在SQL Server 2000之前,也是如此。如果有任何使用此代码,则需要进行修复。
HLGEM,2009年


12

基本上,当FROM子句列出如下表时:

SELECT * FROM
  tableA, tableB, tableC

结果是表A,B,C中所有行的叉积。然后应用限制WHERE tableA.id = tableB.a_id,该限制将丢弃大量行,然后进一步... AND tableB.id = tableC.b_id然后您应该只获取您真正感兴趣的行在。

DBMS知道如何优化此SQL,以便使用JOIN编写此SQL的性能差异可以忽略不计(如果有的话)。使用JOIN表示法使SQL语句更具可读性(恕我直言,不使用联接将语句弄得一团糟)。使用叉积,您需要在WHERE子句中提供连接条件,这就是表示法的问题。您在WHERE子句中挤满了诸如

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

仅用于限制叉积。WHERE子句应仅对结果集包含RESTRICTIONS。如果将表联接条件与结果集限制混合使用,您(和其他人)将发现您的查询更难阅读。您绝对应该使用JOIN并将FROM子句保留为FROM子句,并将WHERE子句保留为WHERE子句。


10

首选第二种方法,因为通过忘记放置where子句不太可能导致意外的交叉连接。没有on子句的联接将失败语法检查,没有no子句的旧式联接不会失败,它将进行交叉联接。

另外,当您以后需要左连接时,它们都处于相同的结构对于维护很有帮助。自1992年以来,旧语法已经过时,现在已经停止使用它了。

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


6

我认为在此页面上采用第二种方法(使用显式JOIN)有一些充分的理由。关键在于,当从WHERE子句中删除JOIN条件时,在WHERE子句中查看其余选择条件变得容易得多。

在真正复杂的SELECT语句中,使读者更容易理解正在发生的事情。


5

SELECT * FROM table1, table2, ...几个表的语法都可以,但是它成指数增长(不一定是数学上准确的语句)难当解读为表数量的增加。

JOIN语法较难写(在一开始),但是它使它明确表明哪些条件影响哪些表。这使得犯错误变得更加困难。

另外,如果所有联接均为INNER,则两个版本均等效。但是,一旦您在语句中的任何地方都建立了OUTER连接,事情就会变得更加复杂,这实际上可以保证您编写的内容不会查询您认为的内容。


2

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

甲骨文:

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

MSSQLServer(尽管在2000版中已弃用)/ Sybase:

SELECT a.foo, b.foo
  FROM a, b
 WHERE a.x *= b.x

但是回到您的问题。我不知道答案,但它可能与一个事实,即加入更自然(语法,至少)比增加的表达到哪里,当你正在做的正是子句:加盟


SQL Server已经弃用了左联接语法,即使在SQL Server 2000中,它也不会始终如一地给出正确的结果(有时会进行交叉联接而不是左联接),并且永远不要在SQL Server中使用。
HLGEM,2009年

@HLGEM:感谢您提供信息。我将更新我的帖子以反映您的意思。
巴勃罗·圣克鲁斯

0

我听到很多人抱怨第一个太难理解了,不清楚。我没有发现任何问题,但是在进行了讨论之后,为了清楚起见,我甚至在INNER JOINS上也使用了第二个。


1
我养成了不使用JOIN语法并以第一方式进行操作的习惯。我必须承认,我经常仍然习惯于这种习惯,只是因为我认为我的大脑已经习惯了遵循这种逻辑,有时对我来说,很难
理解联接语法。– TheTXI

3
我也被这样教过。我更改了编码风格,因为人们会看着它,而又不容易意识到发生了什么。由于没有逻辑上的差异,我找不到选择前者的理由,因此我觉得我应该适应使代码更清晰,以帮助其他人理解我写的东西。
kemiller2002

0

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


0

好吧,第一个查询和第二个查询可能会产生不同的结果,因为LEFT JOIN包含了第一个表中的所有记录,即使右表中没有相应的记录也是如此。

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.