程序上将子查询转换为连接

时间:2009-11-20 19:04:26

标签: sql algorithm join subquery relational-model

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

7 个答案:

答案 0 :(得分:23)

将子查询转换为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

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

SELECT子句中的

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 JOINŠ

您可能已经注意到我没有任何LEFT JOIN示例 - 只有当子查询中的列在这几天几乎任何数据库上都使用NULL测试(COALESCE时,这只是必需的,Oracle的NVLNVL2,MySQL IFNULL,SQL Server的ISNULL等......):

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标准是什么。一旦您知道所涉及的列,您就知道所涉及的表格。

答案 1 :(得分:10)

这个问题依赖于关系代数的基本知识。你需要问问自己正在进行什么样的连接。例如,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来检查它。在哪里存在类似的东西。 (这是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会议上就这类事情进行过一次会议。这是录制的,所以你应该能够在某个时刻观看它(如果你能忍受我的笑话!)

答案 2 :(得分:3)

通常是可能的,查询优化器可以自动执行此操作,因此您无需关心它。

答案 3 :(得分:2)

处于非常高的水平。将子查询转换为JOIN:

  1. FROM:表名称进入FROM
    • JOIN WHERE子句中两边都有表名的部分决定了(a)JOIN的类型(b)连接条件
    • WHERE 两侧没有表名的where子句的部分进入WHERE子句
    • SELECT 来自Sub-Query的列名进入SELECT
  2. 将JOIN转换为子查询需要与上述逻辑

    相反

答案 4 :(得分:2)

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

当我以传统的人性化方式执行此操作时,它非常简单 - 特别是如果子查询已经是别名的 - 它可以首先被拉入公用表表达式。

答案 5 :(得分:1)

这是一个强烈的“依赖”。

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

在另一个层面,我会说“没办法”。大多数时候我写一个子查询,这是因为我想不出一种方法将它楔入“主”查询。这很少涉及相关的子查询,但更常见的是涉及标准的专有扩展,我非常确定。你怎么能解释枢轴,非活动,排名函数,TOP N条款(可能是ANSI标准,我承认从来没有阅读过它们的封面),完整或外部应用等等?这只是SQL Server的一部分,我确信Oracle,DB2,MYSQL和大多数其他播放器都有自己的扩展,打破了“纯粹的”关系模型。

当然,他们说不可能证明是消极的。我总结一下“除非得到证实,否则无法完成”,将证据留给学者和理论家,并指出即使在那时,你购买的任何系统都不会支持它,除非它对制造商有经济意义。在它工作。(有没有系统支持OUTER UNION?)

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

答案 6 :(得分:1)

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

可能会有一些令人惊讶的好坏表现反响。有时子查询比连接快得多。有时它是相反的。