问题
我需要更好地理解关于何时可以在子查询中引用外部表以及何时(以及为什么)这是一个不合适的请求的规则。我在试图重构的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
不幸的是,表2有时有重复的记录,所以我需要先聚合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子句)。我正在使用Oracle,但我认为这将是全面的标准。子句评估有一个明确的顺序(FROM是第一个),所以我认为任何出现在列表后面的子句都可以访问以前处理过的所有信息。我只能假设我的第二个查询以某种方式更改了顺序,所以我的子查询评估得太早了?
此外,我还发现了一个类似的问题(在子查询中引用外部查询的表)但是,尽管他们的意见很好,但他们从来没有真正解释过他为什么不能做他正在做的事情,只是给出了解决问题的替代方案。我已经尝试了他们的替代解决方案,但这给我带来了其他问题。也就是说,带有日期引用的子查询是整个操作的基础,所以我无法摆脱它
问题
我想了解我在这里做了什么。。。为什么我的初始子查询可以看到外部表,但在我将整个语句包装在子查询中之后却看不到?
也就是说,如果我试图做的事情无法完成,那么重构第一个查询以消除重复的最佳方法是什么?我应该引用表1两次吗(需要所有重复)?或者有(可能)更好的方法来解决这个问题吗?
提前感谢!
------编辑------
正如一些人猜测的那样,上面的这些查询实际上不是我正在重构的查询,而是我遇到的问题的一个例子。我正在处理的查询要复杂得多,所以我很犹豫是否将其发布在这里,因为我担心这会让人们偏离正轨。
------更新------
因此,我由一位开发人员同事运行了这个程序,他对为什么我的子查询无法访问t1有一个可能的解释。因为我将这个子查询封装在括号中,所以他认为这个子查询是在我的表t1被求值之前被求值的。这肯定可以解释"ORA-00904:"t1"。"id":我收到的标识符无效错误。这也表明,就像运算的算术顺序一样,在语句中添加parens会使其在某些子句求值中具有优先级。如果专家同意/不同意,我仍然希望他们能参与进来,这是我在这里看到的合乎逻辑的解释。
所以我是根据马丁·史密斯在上面的评论(感谢马丁!)找到这一点的,我想确保我把我的发现分享给其他遇到这个问题的人。
技术注意事项
首先,如果我使用正确的术语来描述我的问题,那肯定会有所帮助:我上面的第一句话使用了一个相关的子查询:
- 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中通过不同的语法提供:
- Oracle中SQL Server APPLY的等效功能是什么
最终,我将重新评估我对这个查询的整个方法,这意味着我必须从头开始重建它(信不信由你,我最初并没有创建这个怪物——我发誓!)非常感谢所有发表评论的人-这确实让我很困惑,但所有的投入都帮助我走上了正轨!
下面的查询如何:
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
在第二个示例中,您正试图将t1引用向下传递两个级别。。你不能那样做,你只能把它传下1级(这就是为什么第一级有效的原因)。如果您给出一个更好的示例来说明您正在尝试做什么,我们也可以帮助您重写查询。