在WHERE子查询中看不到FROM子查询的原因是什么?

时间:2018-05-09 13:20:01

标签: sql oracle

考虑:

SELECT DISTINCT student_id
FROM (SELECT  * FROM Grades WHERE dept_id = 'MT') T
WHERE grade = (SELECT MAX(grade) FROM T);

Oracle抱怨WHERE中子查询中的T不是现有表。我知道我可以使用WITH轻松解决这个问题,但我仍然想了解。管理此案例的SQL规则是什么?该规则背后的逻辑是什么?

1 个答案:

答案 0 :(得分:8)

我不确定原因是否重要;它是,并且知道为什么不改变它。

由于SQL是声明性的,因此存在关于范围,执行顺序,优先顺序等的规则。这些规则使基于成本的计划程序能够生成将实际执行的计划。一个这样的规则是您无法在同一组上评估两个独立查询。即使T是一个材料表,引用它两次也会将它作为两个独立的集合引入计划中。

相反,您需要一种不同的方式来表达您的要求,这更符合语言。一个你不试图多次解析同一个集合的地方。

例如,您可以通过这种方式从相同的 表达式 中获取两个 ... < / p>

WITH
  T AS
(
  SELECT * FROM Grades WHERE dept_id = 'MT'
)
SELECT DISTINCT student_id
FROM T
WHERE grade = (SELECT MAX(grade) FROM T);

或者,您可以使用窗口函数并允许内部引擎确定以最低成本评估所有术语的最佳方式......

SELECT
  *
FROM
(
  SELECT
    Grades.*,
    MAX(grade) OVER ()   AS max_grade
  FROM
    Grades
  WHERE
    dept_id = 'MT'
)
  T
WHERE
  grade = max_grade



非常长的编辑: 反对该提案的主观和客观论点


建议外部查询中定义的集合可用作内部查询中的独立集合。

SELECT DISTINCT
  student_id
FROM
(
  SELECT  * FROM Grades WHERE dept_id = 'MT'
)
  newSetDefinition
WHERE
  grade = (SELECT MAX(grade) FROM newSetDefinition)

-----------------------------
Functionally Equivalent To...
-----------------------------

WITH
  newSetDefinition
AS
(
  SELECT  * FROM Grades WHERE dept_id = 'MT'
)
SELECT DISTINCT
  student_id
FROM
  newSetDefinition
WHERE
  grade = (SELECT MAX(grade) FROM newSetDefinition)


这意味着以下内容也应该有效......

SELECT DISTINCT
  newSetDefinition.student_id
FROM
(
  SELECT  * FROM Grades WHERE dept_id = 'MT'
)
  newSetDefinition
INNER JOIN
(
  SELECT MAX(grade) AS maxGrade FROM newSetDefinition
)
  newSetSummary
    ON newSetSummary.maxGrade = newSetDefinition.grade

-----------------------------
Functionally Equivalent To...
-----------------------------

WITH
  newSetDefinition
AS
(
  SELECT  * FROM Grades WHERE dept_id = 'MT'
)
SELECT DISTINCT
  newSetDefinition.student_id
FROM
  newSetDefinition
INNER JOIN
(
  SELECT MAX(grade) AS maxGrade FROM newSetDefinition
)
  newSetSummary
    ON newSetSummary.maxGrade = newSetDefinition.grade

到目前为止,非常好......


使用嵌套查询会变得有点模糊,因为由于不同的范围可用性和命名冲突,以下内容无法准确表示CTE。有必要在子查询中定义CTE ......

SELECT
  *
FROM
(
  SELECT * FROM table WHERE something = 1
)
  smeg
INNER JOIN
(
  SELECT
    *
  FROM
  (
    SELECT * FROM table WHERE somethingElse = 2
  )
    smeg
  INNER JOIN
  (
    SELECT MAX(id) AS maxID FROM smeg
  )
    smegSummary
      ON smegSummary.maxID = smeg.ID
)
  smegSubSet
    ON smegSubSet.parentID = smeg.ID

-----------------------------
Functionally Equivalent To...
-----------------------------

WITH
   smeg AS
(
  SELECT * FROM table WHERE something = 1
)
SELECT
  *
FROM
  smeg
INNER JOIN
(
  WITH
    smeg AS
  (
    SELECT * FROM table WHERE something = 1
  )
  SELECT
    *
  FROM
    smeg
  INNER JOIN
  (
    SELECT MAX(id) AS maxID FROM smeg
  )
    smegSummary
      ON smegSummary.maxID = smeg.ID
)
  smegSubSet
    ON smegSubSet.parentID = smeg.ID

好的,这没关系,有点不整洁。 CTE有助于避免需要深度嵌套,因此对CTE使用嵌套语法是“混乱的”,但即使这是一个主观测量。

当您看到“set reference”时,您会查看外部查询,直到找到具有该别名的集合,如果找不到,则使用常规规则; CTES,然后是当前模式中的表/视图,然后是当前数据库中的表/视图,但不同的模式,都考虑了权限等。

精细,相当标准的范围规则。


但是下一个场景更客观地存在问题...

SELECT
  *
FROM
(
  SELECT * FROM smeg WHERE something = 1
)
  smeg

在当前的ANSI-SQL中,如果有一个名为smeg的表,这很好。

在AlwaysLearning-SQL中,它是一个循环引用。 smeg的“最近”定义是外部查询。 “覆盖”名为smeg的任何表或视图。因此,内部查询现在从......本身选择......

有一种说法是“只是让它引发循环引用错误”。

但这会破坏向后兼容性。

想象一下,如果Oracle将此功能添加到v13中吗?以前突然发生的查询开始引发循环引用错误?为什么?为了使一些子查询像CTE一样工作,假设这样做有帮助/方便吗? To make some aspects of life "convenient" we broke some of your queries.

发生向后兼容性的破坏。但只有当收益 超过后果时。

在这种情况下,可以通过CTE完成 任何 可以完成您的建议。并且添加了CTE而没有破坏任何遗留行为。 (主观/可论证) CTE可以通过更结构化,更易维护,更易于阅读,更易于调试等方式实现这一目标。

我个人非常高兴没有人打破一些问题来实现一些非常小众的功能。