【问题标题】:Will ANSI JOIN vs. non-ANSI JOIN queries perform differently?ANSI JOIN 与非 ANSI JOIN 查询的执行方式是否不同?
【发布时间】:2009-10-21 06:28:47
【问题描述】:

我的业务逻辑包含在大约 7000 行 T-SQL 存储过程中,其中大多数都有下一个 JOIN 语法:

SELECT A.A, B.B, C.C
FROM aaa AS A, bbb AS B, ccc AS C
WHERE
    A.B = B.ID
AND B.C = C.ID
AND C.ID = @param

如果我将这样的查询替换为以下内容,我是否会获得性能提升:

SELECT A.A, B.B, C.C
FROM aaa AS A
JOIN bbb AS B
   ON A.B = B.ID
JOIN ccc AS C
   ON B.C = C.ID
   AND C.ID = @param

或者它们是一样的?

【问题讨论】:

  • 我不知道你为什么编辑标题:它现在包含错误陈述。这两个查询都是连接,它们各自的语法都包含在 SQL-92 标准中。请注意,除了美国 (ANSI) 标准之外,该标准还具有国际标准 (ISO)。
  • 来自SQL 上的维基百科文章:“SQL 于 1986 年作为 SQL-86 被美国国家标准协会 (ANSI) 采用为标准,并于 1987 年被国际标准化组织 (ISO) 采用。”在文献中,限定词 ISO/IEC 似乎远比 ANSI 普遍;我认为 ANSI 在 Stackoverflow 等论坛中很流行,因为 SQL Server 和 MySQL 等产品的关键字使用“ANSI”一词,例如ANSI_NULLS(我不知道他们为什么不使用“ISO”);虽然这些是我自己的观察,但我怀疑它们是原始想法;)
  • 进一步注意,每个标准都采用(并添加到)以前的标准 SQL 的特性,因此标准 SQL 特性永远不会被弃用,Hugh Darwen 将其称为Shackle of Compatibility
  • 您可以通过阅读每个标准的规范来证明上述内容;)关于两个查询都是标准 SQL 的证明:使用 Mimer SQL-92 Validator:将每个查询一次复制并粘贴到框中,编辑以删除专有的 SQL Server 语法 @param(例如,将其更改为文字值 1),然后单击“测试 SQL”。两者都会生成结果“Transitional SQL-92”。
  • 您可以分别使用SQL-99 validator 和[SQL:2003 验证器](developer.mimer.com/validator/parser200x/index.tml) 重复上述操作。我假设您没有要求我证明它们既是连接又是语义等价的,这是任何半体面的优化器都应该认识到的事实,因为这在接受的答案中有所说明。

标签: sql-server tsql join sql


【解决方案1】:

这两个查询是相同的,除了第二个是 ANSI-92 SQL 语法,第一个是不包含 join 子句的旧 SQL 语法。它们应该产生完全相同的内部查询计划,尽管您可能想检查一下。

出于多种原因,您应该使用 ANSI-92 语法

  • 使用 JOIN 子句分隔 从关系逻辑 过滤逻辑(WHERE),因此是 更简洁、更易于理解。
  • 这个特定的查询无关紧要,但在某些情况下,旧的外连接语法(使用 + )是不明确的,因此查询结果取决于实现 - 或者根本无法解析查询。 ANSI-92 不会出现这些情况
  • 这是一种很好的做法,因为现在大多数开发人员和 dba 都将使用 ANSI-92,您应该遵循该标准。当然,所有现代查询工具都会生成 ANSI-92。
  • 正如@gbn 所指出的,它确实倾向于避免意外的交叉连接。

我本人反对 ANSI-92 有一段时间了,因为旧语法在概念上有一点优势,因为它更容易将 SQL 设想为使用的所有表的大规模笛卡尔连接,然后进行过滤操作 - 一种心理技术对于掌握 SQL 查询正在做什么很有用。然而,几年前我决定我需要与时俱进,经过一段相对较短的调整期后,我现在非常喜欢它——主要是因为上面给出的第一个原因。唯一应该偏离 ANSI-92 语法,或者更确切地说不使用该选项的地方是自然连接,这隐含危险。

【讨论】:

  • 我在 10 年前也曾有过类似的攀登倾向,因为我放弃了旧的逗号/等号形式的联接,但没有回头。
  • 检查大型查询时,查看靠近相关表的连接逻辑会很有帮助。较旧的语法可能需要大量上下滚动才能同时检查表和谓词。
  • 作为对避免交叉连接的进一步评论,显式连接使得在通过复合键连接时更容易发现丢失的谓词。当大型复合键的最后一列具有非常低的基数时,这种缺失的谓词可能会在极端情况下导致潜在的问题,因此在测试期间它们并不总是很明显。更简洁的语法使这些问题在开发时更加明显。
【解决方案2】:

第二种构造在 SQL 社区中被称为“中缀连接语法”。第一个构造 AFAIK 没有被广泛接受的名称,所以我们称之为“旧式”内连接语法。

通常的论点是这样的:

“传统”语法的优点: 谓词在WHERE 子句中物理分组在一起 使查询的一般顺序,特别是 n 元关系,更易于阅读和理解(中缀语法的 ON 子句可以展开谓词,因此您必须在一个表或列上查找一个表或列的外观视距)。

“传统”语法的缺点:省略“连接”谓词之一时不会出现解析错误,结果是笛卡尔积(在中缀语法中称为 CROSS JOIN),这样的错误可能是难以检测和调试。此外,'join' 谓词和 'filtering' 谓词在物理上组合在 WHERE 子句中,这可能会导致它们相互混淆。

【讨论】:

    【解决方案3】:

    两个查询相等 - 第一个是使用非 ANSI JOIN 语法,第二个是 ANSI JOIN 语法。我建议坚持使用 ANSI JOIN 语法。

    是的,当您要加入的表可能不包含任何匹配的记录时,您要使用 LEFT OUTER JOIN(顺便说一句,这也是 ANSI JOIN 语法)。

    参考:Conditional Joins in SQL Server

    【讨论】:

      【解决方案4】:

      好的,它们执行相同。这是同意的。 与许多人不同,我使用较旧的约定。 SQL-92“更容易理解”是有争议的。已经编写了 40 年的编程语言(gulp),我知道“易于阅读”首先是在任何其他约定之前开始的,首先是“视觉敏锐度”(这里误用了术语,但这是我能用的最好的短语)。 在阅读 SQL 时,您首先关心的是涉及哪些表,然后是哪个表(大多数)定义了粒度。然后你关心数据的相关约束,然后是选择的属性。虽然 SQL-92 主要将这些想法分离出来,但有很多噪音词,大脑必须解释和处理这些词,这使得阅读 SQL 变得更慢。

      SELECT Mgt.attrib_a   AS attrib_a
            ,Sta.attrib_b   AS attrib_b
            ,Stb.attrib_c   AS attrib_c
      FROM   Main_Grain_Table  Mgt
            ,Surrounding_TabA  Sta
            ,Surrounding_tabB  Stb
      WHERE  Mgt.sta_join_col  = Sta.sta_join_col
      AND    Mgt.stb_join_col  = Stb.stb_join_col
      AND    Mgt.bus_logic_col = 'TIGHT'
      

      视力! 将新属性的逗号放在前面它也使注释代码更容易 对函数和关键字使用特定大小写 为表格使用特定案例 对属性使用特定大小写 垂直排列操作员和操作 使 FROM 中的第一个表代表数据的粒度 使 WHERE 的第一个表成为连接约束,并让特定的紧密约束浮动到底部。 为数据库中的所有表选择 3 个字符的别名,并在您引用该表的任何地方使用别名。您也应该使用该别名作为该表上(许多)索引的前缀。 1 1/2 打中的 6 个,对吧?也许。但即使您使用 ANSI-92 约定(我已经使用并且在某些情况下将继续这样做)使用视力原则,垂直对齐让您的注意力转移到您想看的地方并轻松避开事物(特别是噪音词)你不需要。

      【讨论】:

        【解决方案5】:

        执行两者并检查它们的查询计划。他们应该是平等的。

        【讨论】:

        • +1 使用分析工具。我建议查看:rpbouman.blogspot.com/2006/04/…> 以了解更多指定连接的“糖分”方式。让标准成为您的指南,但请记住,您的特定 DB 实现是路径(希望它不是 MySQL :)
        【解决方案6】:

        在我看来,FROM 子句是我决定我的 SELECT 子句要处理的行中需要哪些列的地方。它是表达业务规则的地方,它将把计算所需的值带到同一行。业务规则可以是拥有发票的客户,从而产生包括负责客户在内的发票行。它也可以是与客户位于同一邮政编码的场所,从而产生一个靠近的场所和客户的列表。

        这是我计算结果集中行的中心度的地方。毕竟,我们只是在 RDBMS 中看到了一个列表的隐喻,每个列表都有一个主题(实体),每一行都是实体的一个实例。如果了解行中心性,则了解结果集的实体。

        WHERE 子句从概念上讲是在 from 子句中定义行之后执行的,它剔除 SELECT 子句处理不需要的行(或包括需要的行)。

        因为连接逻辑可以在 FROM 子句和 WHERE 子句中表达,而且子句的存在是为了分治复杂的逻辑,所以我选择将涉及列值的连接逻辑放在 FROM 子句中,因为这本质上是表示由列中的匹配值支持的业务规则。

        即我不会写这样的 WHERE 子句:

         WHERE Column1 = Column2
        

        我会将它放在 FROM 子句中,如下所示:

         ON Column1 = Column2
        

        同样,如果要将列与外部值(可能在列中也可能不在列中的值)进行比较,例如将邮政编码与特定邮政编码进行比较,我会将其放在 WHERE 子句中,因为我本质上是在说我只想要这样的行。

        即我不会写这样的 FROM 子句:

         ON PostCode = '1234'
        

        我会将它放在 WHERE 子句中,如下所示:

         WHERE PostCode = '1234'
        

        【讨论】:

        • ON <SOMECONDTION> AND PostCode = '1234' 可以在 left joins 上用于条件连接,但不排除 PostCode '1234' 的行
        【解决方案7】:

        ANSI 语法既不强制将谓词放置在正确的子句(即 ON 或 WHERE)中,也不强制 ON 子句与相邻表引用的关联。开发者可以随意写出这样的烂摊子

        SELECT
           C.FullName,
           C.CustomerCode,
           O.OrderDate,
           O.OrderTotal,
           OD.ExtendedShippingNotes
        FROM
           Customer C
           CROSS JOIN Order O
           INNER JOIN OrderDetail OD
              ON C.CustomerID = O.CustomerID
              AND C.CustomerStatus = 'Preferred'
              AND O.OrderTotal > 1000.0
        WHERE
           O.OrderID = OD.OrderID;
        

        说到“将生成 ANSI-92”的查询工具,我在这里评论是因为它生成了

        SELECT 1
           FROM DEPARTMENTS C
                JOIN EMPLOYEES A
                     JOIN JOBS B
             ON C.DEPARTMENT_ID = A.DEPARTMENT_ID
             ON A.JOB_ID = B.JOB_ID
        

        逃避传统“restrict-project-cartesian product”的唯一语法是外连接。此操作更复杂,因为它不是关联的(与自身和普通连接)。至少,必须明智地用外连接将查询括起来。但是,这是一种奇特的操作;如果您经常使用它,我建议您学习关系数据库课程。

        【讨论】:

        • 我认为您的第二个示例语法无效。
        • 我看不出外连接是一个“异国情调”的操作。
        猜你喜欢
        • 1970-01-01
        • 2019-11-18
        • 1970-01-01
        • 2011-12-10
        • 2012-06-26
        • 2020-08-23
        • 2021-11-15
        • 2020-06-15
        • 1970-01-01
        相关资源
        最近更新 更多