SQL - 子查询和外部表之间的关系

Posted

技术标签:

【中文标题】SQL - 子查询和外部表之间的关系【英文标题】:SQL - Relationship between a SubQuery and an Outer Table 【发布时间】:2013-12-05 01:50:31 【问题描述】:

问题

我需要更好地了解有关何时可以在子查询中引用外部表以及何时(以及为什么)这是不适当的请求的规则。我在尝试重构的 Oracle SQL 查询中发现了一个重复项,但是当我尝试将引用的表转换为分组子查询时遇到了问题。

以下语句适用:

SELECT  t1.*  
FROM    table1 t1, 
INNER JOIN table2 t2 
        on t1.id = t2.id        
        and t2.date = (SELECT max(date) 
                       FROM   table2  
                       WHERE  id = t1.id) --This subquery has access to t1

不幸的是,table2 有时会有重复记录,所以我需要先聚合 t2,然后再将其加入 t1。但是,当我尝试将其包装在子查询中以完成此操作时,SQL 引擎突然无法再识别外部表。

SELECT  t1.* 
FROM    table1 t1, 
INNER JOIN (SELECT * 
            FROM  table2 t2
            WHERE t1.id = t2.id              --This loses access to t1
              and t2.date = (SELECT max(date) 
                             FROM   table2 
                             WHERE  id = t1.id)) sub on t1.id = sub.id 
                             --Subquery loses access to t1

我知道这些是根本不同的查询,我要求编译器将它们放在一起,但我不明白为什么一个可以工作,而另一个不行。

我知道我可以在我的子查询中复制表引用并有效地将我的子查询从外部表中分离出来,但这似乎是完成这项任务的一种非常丑陋的方式(代码和处理的所有重复)。

有用的参考资料

我发现了对 SQL Server 中子句执行顺序的精彩描述:(INNER JOIN ON vs WHERE clause)。我正在使用 Oracle,但我认为这将是全面的标准。子句评估有一个明确的顺序(首先是 FROM),所以我认为任何出现在列表后面的子句都可以访问之前处理的所有信息。我只能假设我的第二个查询以某种方式改变了排序,因此我的子查询被评估得太早了?

另外,我发现问了一个类似的问题(Referencing outer query's tables in a subquery ) 但是虽然输入很好,但他们从未真正解释过为什么他不能做他正在做的事情,而只是为他的问题提供了替代解决方案。我已经尝试过他们的替代解决方案,但它给我带来了其他问题。也就是说,带有日期引用的子查询是整个操作的基础,所以我无法摆脱它。

问题

我想了解我在这里做了什么...为什么我的初始子查询可以看到外部表,但在我将整个语句包装在子查询中之后却看不到?

也就是说,如果我试图做的事情无法完成,那么重构第一个查询以消除重复的最佳方法是什么?我应该两次引用 table1 (所有需要的重复项)吗?还是有(可能)更好的方法来解决这个问题?

提前致谢!

-----编辑------

正如一些人推测的那样,上面的这些查询并不是我正在重构的实际查询,而是我遇到的问题的一个示例。我正在处理的查询要复杂得多,所以我不愿在此处发布它,因为我担心它会让人们偏离正轨。

-----更新------

所以我由一位开发人员运行了这个,他有一个可能的解释为什么我的子查询无法访问 t1。因为我将此子查询包装在括号中,所以他认为在评估我的表 t1 之前正在评估此子查询。这肯定会解释我收到的 'ORA-00904: "t1"."id": invalid identifier' 错误。它还表明,就像运算的算术顺序一样,在语句中添加括号会在某些子句评估中赋予它优先级。如果专家同意/不同意这是我在这里看到的合乎逻辑的解释,我仍然希望专家参与进来。

【问题讨论】:

无法关联派生表。它必须独立存在。虽然你可以加入它。您可以在需要某种相关派生表的地方使用APPLY 马丁....真诚的,谢谢!根据您的评论,我能够进行一些额外的研究,发现我的查询实际上并没有按照我最初的想法提取数据。此外,您使用 Apply 的建议似乎非常适用(尽管对我而言,语法与我使用 Oracle 时略有不同)。非常感谢您的建议 - 事实上,如果您将其作为答案提交,我会将其标记为正确的。 【参考方案1】:

下面的查询怎么样:

SELECT t1.* FROM 
(
  SELECT * 
  FROM 
  (
    SELECT t2.id,
    RANK() OVER (PARTITION BY t2.id, t2.date ORDER BY t2.date DESC) AS R  
    FROM table2 t2
  )
  WHERE R = 1
) sub 
INNER JOIN table1 t1 
ON t1.id = sub.id

【讨论】:

感谢您指出这一点 - 我已经编辑了上面的示例。我实际上并没有在第二个查询中引入 t2.* 。这是这篇文章的错字 不,它给出了相同的编译错误。如果 Inner Join 引用子查询而不明确说明 From table1,则它无法识别 t1。具体错误是 ORA-00904: "t1"."id": invalid identifier 这是一个不错的尝试,但我仍然遇到同样的错误。问题在于它被评估的顺序......它试图在定义 t1 之前评估该子查询,这就是无法识别 t1 的原因。 嘿!干得好,它编译了(为此+1!)!我将不得不使用这种方法,看看我是否可以让它做我需要的事情......不幸的是,这个解决方案目前表现不佳(我从未提到过,但 table2 中有 26+ 百万条记录,所以它是一个工作的野兽和)。感谢您的帮助……您肯定让我想到了解决问题的不同方法!谢谢!【参考方案2】:

在您的第二个示例中,您试图将 t1 引用向下传递 2 个级别。您不能这样做,您只能将其向下传递 1 个级别(这就是第一个有效的原因)。如果您提供了一个更好的示例来说明您正在尝试做什么,我们也可以帮助您重写您的查询。

【讨论】:

我一开始也是这么想的,但是如果我用一个日期替换 (SELECT max(date) FROM table2 WHERE id = t1.id)) ,它仍然无法编译。问题是直接在引用外部表的子查询上进行内部连接。但为什么呢?【参考方案3】:

因此,我根据 Martin Smith 上面的评论(感谢 MARTIN!)想出了这一点,并且我想确保将我的发现分享给遇到此问题的其他人。

技术考虑

首先,如果我使用正确的术语来描述我的问题肯定会有所帮助:我上面的第一条语句使用了相关子查询

http://en.wikipedia.org/wiki/Correlated_subquery http://www.programmerinterview.com/index.php/database-sql/correlated-vs-uncorrelated-subquery/

这实际上是一种相当低效的拉回数据的方法,因为它会为外部表中的每一行重新运行子查询。出于这个原因,我将寻找在我的代码中消除这些类型的子查询的方法:

https://blogs.oracle.com/optimizer/entry/optimizer_transformations_subquery_unesting_part_1

另一方面,我的第二条语句是在 Oracle 中使用所谓的内联视图,在 SQL Server 中也称为派生表

http://docs.oracle.com/cd/B19306_01/server.102/b14200/queries007.htm http://www.programmerinterview.com/index.php/database-sql/derived-table-vs-subquery/

内联视图/派生表在查询开始时创建一个临时的未命名视图,然后将其视为另一个表,直到操作完成。因为编译器在看到 FROM 行上的这些子查询时需要创建一个临时视图,所以这些子查询必须是完全自包含的,在子查询之外没有任何引用。

为什么我的所作所为很愚蠢

我在第二个表中试图做的实际上是基于对另一个表的模棱两可的引用创建一个视图,该引用超出了我的陈述知识范围。这就像试图引用您在查询中未明确说明的表中的字段一样。

解决方法

最后,值得注意的是,Martin 提出了一种相当聪明但最终效率低下的方法来完成我想要做的事情。 Apply 语句是一个专有的 SQL Server 函数,但它允许您与派生表之外的对象对话:

http://technet.microsoft.com/en-us/library/ms175156(v=SQL.105).aspx

同样,此功能可通过不同的语法在 Oracle 中使用:

What is the equivalent of SQL Server APPLY in Oracle?

最终,我将重新评估我对这个查询的整个方法,这意味着我必须从头开始重建它(信不信由你,我最初并没有创造这个怪物——我发誓!)。 非常感谢所有发表评论的人 - 这确实让我很困惑,但所有的意见都帮助我走上了正轨!

【讨论】:

+1 将它们视为内联视图很好地解释了问题。 More recent versions of Oracle do support APPLY though 感谢您的信息...我仍在使用 11G,因此功能对我来说是新闻! 不错的答案,但仍然无法在内联视图和相关查询之间获得明显优势,何时将查询视为此,何时视为? @osamayaccoub 我现在看到你的困惑了......是的,我上面的子查询已经在JOIN 上,虽然该子句在这种情况下是一个实体,但可以将其视为FROM 子句。专注于解析器将如何剖析我的代码并创建执行计划。评估FROMJOIN 子句将是第一步,因为它需要知道要管理哪些资源。但是当它试图提取它的初始资源并创建一个临时表/视图(在幕后)来容纳我的子查询数据时,我试图引用它尚未评估的值。 @osamayaccoub Martin 在上面描述得最好......“派生表无法关联。”我试图让解析器为我创建一个临时表,作为我的 FROM/JOIN 子句的一部分......所以不允许关联。

以上是关于SQL - 子查询和外部表之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

SQL 子查询

Hive中内部表外部表分区表分桶表之间的关系

为啥 SQL 子查询中的外部引用会产生不同的结果?

子查询中的 sqlalchemy 表冒泡到外部查询中

SQL中的外部键约束有啥用?

那个mysql 子查询和连接查询 一般常用哪个 谁效率高些