14

问题

我需要更好地理解有关何时可以在子查询中引用外部表以及何时(以及为什么)这是不适当的请求的规则。我在尝试重构的 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 子句)。我正在使用 Oracle,但我认为这将是全面的标准。子句评估有一个明确的顺序(首先是 FROM),所以我认为任何出现在列表后面的子句都可以访问之前处理的所有信息。我只能假设我的第二个查询以某种方式改变了排序,因此我的子查询被评估得太早了?

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

问题

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

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

提前致谢!

- - - 编辑 - - -

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

- - - 更新 - - -

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

4

3 回答 3

6

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

技术考虑

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

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

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

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

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

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

解决方法

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

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

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

于 2013-11-21T16:54:11.030 回答
0

下面的查询怎么样:

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
于 2013-11-20T20:50:34.297 回答
-1

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

于 2013-11-20T20:52:54.547 回答