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

Posted

技术标签:

【中文标题】SQL 左连接与 FROM 行上的多个表?【英文标题】:SQL left join vs multiple tables on FROM line? 【发布时间】:2010-10-28 00:38:27 【问题描述】:

大多数 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

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

【问题讨论】:

Guffa:你是怎么发现的?虽然我的问题是比“我该怎么做”更多的最佳实践 由于这是最佳实践,请将其设为 Wiki。 我认为没有人评论这两者的表现。任何人都可以确认或引用任何关于任何重大差异的合理信息吗? @ahnbizcad 这两个给定的查询不做同样的事情。第一个返回与 INNER JOIN ON 相同。实现是特定于 DBMS 版本的,即使这样也几乎没有保证。但是 DBMS 转换等效逗号、INNER JOIN ON/WHERE 和 CROSS JOIN WHERE 的情况是微不足道的。了解关系数据库查询优化/实现。 有资源推荐吗?巨大而密集的手册是我尝试从这里学习的原因。 【参考方案1】:

基本上,当您的 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 子句。

【讨论】:

【参考方案2】:

旧的语法,只是列出表,并使用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 and INNER JOIN 是未来的潮流。

【讨论】:

“在大多数现代数据库中已被弃用。” --- 只是好奇,哪些? 请原谅,我对 *= 运算符不熟悉,它有什么作用?谢谢! Star= 和 =Star 是(很好)左右外连接,还是左右外连接?已被弃用多年,自 SQL Server 6 以来我就没有使用过它们。 不推荐使用逗号。不推荐使用不标准的 OUTER JOIN 语法 *=/=*/*=* 当然可以,因为旧的语法已被弃用,您不应该使用它。不推荐使用它的原因与外部连接有关,但他们在此过程中不推荐使用整个过程。我认为知道这一点很重要。如果有什么特别的问题,欢迎您提供自己的答案或编辑我的答案。关于查询优化器少做的部分取自 msdn 上的一篇旧文章,我没有链接。【参考方案3】:

我认为这个页面上有一些很好的理由采用第二种方法 - 使用显式 JOIN。但关键是,当从 WHERE 子句中删除 JOIN 条件时,在 WHERE 子句中查看剩余的选择条件变得更加容易。

在非常复杂的 SELECT 语句中,读者更容易理解发生了什么。

【讨论】:

【参考方案4】:

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

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

所以首选第二种方法的原因是,它是根据 ANSI 和 ISO 标准委员会制定的 SQL 标准。

【讨论】:

, 仍然是标准的。 on 只需要为 outer join 引入子选择。【参考方案5】:

当您需要外连接时,第二个语法不是总是需要的:

甲骨文:

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

MSSQLServer(虽然在 2000 版本中是 deprecated)/Sybase:

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

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

【讨论】:

SQL Server 已弃用左连接语法,即使在 SQL Server 2000 中,它也不会始终如一地给出正确的结果(有时它执行交叉连接而不是左连接),并且永远不应该在 SQL Server 中使用. @HLGEM:感谢您提供的信息。我将更新我的帖子以反映您在说什么。【参考方案6】:

SELECT * FROM table1, table2, ... 语法适用于几个表格,但随着表格数量的增加,它变得越来越难以阅读(不一定是数学上准确的陈述)。

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

此外,如果所有联接都是 INNER,则两个版本是等效的。但是,当您在语句中的任何位置使用 OUTER 连接时,事情就会变得更加复杂,并且几乎可以保证您编写的内容不会查询您认为自己编写的内容。

【讨论】:

【参考方案7】:

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

此外,当您以后必须进行左连接时,它们都位于同一结构中有助于维护。而且旧语法自 1992 年以来就已经过时了,早就该停止使用它了。

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

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)

【讨论】:

MS SQLServer 中不推荐使用 *= 语法,这是有充分理由的:它不仅使它更难阅读,而且它不像人们认为的那样做,而且它与一个看起来相似的 LEFT JOIN。 (+) 语法对我来说不熟悉;这是什么 SQL 实现? Oracle 至少使用了另一种语法。 永远不要使用 SQL Server 语法 *=,它不会给出一致的结果,因为它有时会解释为交叉连接而不是左连接。这甚至可以追溯到 SQL Server 2000。如果您有任何代码使用它,则需要修复。【参考方案10】:

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

【讨论】:

【参考方案11】:

我听到很多人抱怨第一个太难理解而且不清楚。我看不出它有什么问题,但是在进行了讨论之后,为了清楚起见,我什至在 INNER JOINS 上使用了第二个。

【讨论】:

我从小养成了不使用 JOIN 语法并以第一种方式进行操作的习惯。我必须承认我仍然经常停留在这个习惯中,因为我认为我的大脑已经习惯于遵循这种逻辑,而连接语法有时对我来说似乎很难思考。 我也是这样教的。我改变了我的编码风格,因为人们会看着它并且不容易识别发生了什么。由于没有逻辑上的差异,我找不到选择前者而不是后者的理由,我觉得我应该适应让代码更清晰,以帮助别人理解我写的内容。

以上是关于SQL 左连接与 FROM 行上的多个表?的主要内容,如果未能解决你的问题,请参考以下文章

SQL:具有相似表的多个左连接

左连接 SQL 查询 - MS Access

使用左连接在 from 子句上添加表

MYSQL:与 Group_Concatenate 一起使用的两个表上的左连接

SQL中的左连接与右连接,内连接有啥区别

具有多个左外连接的 SQL Server 查询挂起