20

是否有将 SQL 子查询转换为连接的通用过程或算法,反之亦然?也就是说,是否有一组印刷操作可以应用于包含子查询的语法正确的 SQL 查询语句,该子查询会导致没有子查询的功能等效语句?如果是这样,它们是什么(即,算法是什么),在什么情况下它们不适用?

4

7 回答 7

29

将子查询转换为 JOIN 可以非常简单:

IN条款

 FROM TABLE_X x
WHERE x.col IN (SELECT y.col FROM TABLE_Y y)

...可以转换为:

FROM TABLE_X x
JOIN TABLE_Y y ON y.col = x.col

您的 JOIN 标准是您可以直接比较的地方。

EXISTS条款

但是,当您查看该EXISTS条款时,会出现一些复杂情况。EXISTS通常是相关的,其中子查询由子查询之外的表中的条件过滤。但 EXISTS 仅用于根据标准返回布尔值。

 FROM TABLE_X x
WHERE EXISTS (SELECT NULL
                FROM TABLE_Y y
               WHERE y.col = x.col)

...转换:

FROM TABLE_X x
JOIN TABLE_Y y ON y.col = x.col

由于布尔值的存在,结果集中存在出现更多行的风险。

SELECTs 在 SELECT 子句中

这些应该总是改变,有偏见

SELECT x.*,
       (SELECT MAX(y.example_col)
          FROM TABLE_Y y
         WHERE y.col = x.col)
  FROM TABLE_X x

您现在可能注意到了一种模式,但我对内联视图示例做了一些不同的处理:

SELECT x.*,
       z.mc
  FROM TABLE_X x
  JOIN (SELECT y.col, --inline view within the brackets
               MAX(y.example_col) 'mc'
          FROM TABLE_Y y
      GROUP BY y.col) z ON z.col = x.col

关键是确保内联视图结果集包括需要加入的列以及列。

LEFT JOINs

您可能已经注意到我没有任何 LEFT JOIN 示例 - 仅当子查询中的列使用 NULL 测试时才有必要(COALESCE这些天几乎在任何数据库上,Oracle's NVLor NVL2, MySQLs IFNULL, SQL Server'sISNULL等......):

SELECT x.*,
       COALESCE((SELECT MAX(y.example_col)
          FROM TABLE_Y y
         WHERE y.col = x.col), 0)
  FROM TABLE_X x

转换:

   SELECT x.*,
          COALESCE(z.mc, 0)
     FROM TABLE_X x
LEFT JOIN (SELECT y.col,
                  MAX(y.example_col) 'mc'
             FROM TABLE_Y y
         GROUP BY y.col) z ON z.col = x.col

结论

我不确定这是否会满足您的排版需求,但希望我已经证明关键是确定 JOIN 标准是什么。一旦您知道所涉及的列,您就知道所涉及的表。

于 2009-11-30T04:39:20.517 回答
12

这个问题依赖于关系代数的基本知识。您需要问自己正在执行哪种联接。例如,LEFT ANTI SEMI JOIN 类似于 WHERE NOT EXISTS 子句。

有些联接不允许复制数据,有些则不允许消除数据。其他人允许额外的字段可用。我在我的博客http://msmvps.com/blogs/robfarley/archive/2008/11/09/join-simplification-in-sql-server.aspx中讨论了这个问题

另外,请不要觉得你需要在 JOIN 中做所有事情。查询优化器应该为您处理所有这些,并且您通常可以使查询更难以这种方式维护。您可能会发现自己使用了一个扩展的 GROUP BY 子句,并且有有趣的 WHERE .. IS NULL 过滤器,它只会将业务逻辑与查询设计断开连接。

SELECT 子句中的子查询(本质上是查找)只提供一个额外的字段,而不是重复或消除。因此,您需要确保在 JOIN 中强制执行 GROUP BY 或 DISTINCT 值,并使用 OUTER JOIN 来保证行为相同。

WHERE 子句中的子查询永远不能重复数据,或者为 SELECT 子句提供额外的列,因此您应该使用 GROUP BY / DISTINCT 来检查这一点。WHERE EXISTS 类似。(这是 LEFT SEMI JOIN)

WHERE NOT EXISTS (LEFT ANTI SEMI JOIN) 不提供数据,也不重复行,但可以消除...为此,您需要执行 LEFT JOIN 并查找 NULL。

但是查询优化器应该为您处理所有这些。实际上,我喜欢在 SELECT 子句中偶尔出现子查询,因为它清楚地表明我没有重复或删除行。QO 可以为我整理,但如果我使用视图或内联表值函数,我想向那些追随我的人明确表示,QO 可以大大简化它。查看原始查询的执行计划,您会看到系统正在为您提供 INNER/OUTER/SEMI 连接。

您真正需要避免的事情(至少在 SQL Server 中)是使用 BEGIN 和 END 的函数(例如标量函数)。他们可能觉得他们简化了您的代码,但实际上它们将在单独的上下文中执行,因为系统将它们视为程序性(不可简化)。

我在最近的SQLBits V会议上做了一个关于这种事情的会议。它被记录下来了,所以你应该可以在某个时候观看它(如果你能忍受我的笑话!)

于 2009-12-04T02:30:43.593 回答
3

这通常是可能的,而且好处是查询优化器可以自动完成,因此您不必关心它。

于 2009-12-01T12:40:47.453 回答
2

在一个非常高的水平。将子查询转换为 JOIN:

  1. FROM:表名进入 FROM
    • JOIN WHERE 子句两边带有表名的部分决定 (a) JOIN 的类型 (b) 连接的条件
    • WHERE where 子句两边没有表名的部分进入 WHERE 子句
    • SELECT子查询中的列名进入 SELECT

将 JOIN 转换为子查询需要与上述逻辑相反

于 2009-11-20T19:16:51.933 回答
2

至少在 SQL Server 中,优化器可以随意执行此操作,但我确信它何时执行此操作是有限制的。我敢肯定,能够在计算机中完成这可能是某人的博士论文。

当我以老式的人工方式执行此操作时,它相当简单——特别是如果子查询已经有别名——它可以首先被拉入公用表表达式。

于 2009-11-20T19:58:05.673 回答
1

这对“视情况而定”的评价很高。

在一个层面上,如果您谈论的是与 ANSI SQL 89 或 92* 兼容的查询,那么我猜这肯定是可能的。如果您有由“基本”select、from 和 where 子句组成的简单(甚至不那么简单)查询,那么是的,我想在数学上定义创建和“取消创建”子查询的过程和过程是可能的(尽管您如何确定何时以算法方式形成子查询超出了我的范围)。我认为这个“基本原理”可以应用于外部连接和相关子查询。

在另一个层面上,我会说“不可能”。大多数时候我写一个子查询,这是因为我想不出一种方法将它嵌入到“主”查询中。这很少涉及相关的子查询,但通常涉及到什么是,我非常确定,标准的专有扩展。你怎么能解释枢轴、非枢轴、排名函数、TOP N 子句(这很可能是 ANSI 标准,我承认我从未从头到尾读过它们)、FULL 或 OUTER APPLY 等等?这只是 SQL Server 的一部分,我确信 Oracle、DB2、MYSQL 和大多数其他玩家都有自己的扩展,它们打破了“纯粹的”关系模型。

当然,他们说不可能证明是否定的。我会总结为“除非得到证明否则无法完成”,将证明留给学者和理论家,并指出即使那样,您购买的任何系统都不会支持它,除非制造商在财务上有意义工作。(是否有任何系统支持 OUTER UNION 呢?)

** 谷歌搜索未能产生对第三个 ANSI SQL 标准的任何引用。我知道我几年前听说过它,它曾经发生过吗?*

于 2009-11-30T04:39:40.390 回答
1

将查询从子查询转换为连接的全自动系统相对难以构建。您需要获取输入查询,将其解析为解析树,然后在解析树上执行一些相当复杂的模式匹配 - 用解析树的新部分替换树的部分。最后,您遍历树以输出新查询。

可能会有一些非常好的或坏的性能影响。有时子查询比连接快得多。有时情况正好相反。

于 2009-12-01T04:30:41.583 回答