内联视图和 WITH 子句之间的区别?

Ksh*_*rma 10 oracle cte derived-tables

内联视图允许您从子查询中进行选择,就好像它是不同的表一样:

SELECT
    *
FROM /* Selecting from a query instead of table */
    (
        SELECT
            c1
        FROM
            t1
        WHERE
            c1 > 0
    ) a
WHERE
    a.c1 < 50;
Run Code Online (Sandbox Code Playgroud)

我已经看到这使用不同的术语来引用:内联视图、WITH 子句、CTE 和派生表。对我来说,它们似乎是同一件事的不同供应商特定语法。

这是一个错误的假设吗?这些之间是否有任何技术/性能差异?

a1e*_*x07 10

Oracle 中的内联视图(派生表)和 WITH 子句 (CTE) 之间存在一些重要区别。其中一些非常通用,即适用于其他 RDBMS。

  1. WITH 可用于构建递归子查询,内联视图 - 不(据我所知,所有支持 CTE 的 RDBMS 都是如此)
  2. WITH子句中的子查询更有可能首先被物理执行;在许多情况下,在WITH和内联视图之间进行选择会使优化器选择不同的执行计划(我猜这是特定于供应商的,甚至可能是特定于版本的)。
  3. WITH可以将子查询具体化为临时表(我不知道是否有任何其他供应商但 Oracle 支持此功能)。
  4. 子查询中WITH可以多次引用,在其他的子查询和主查询(真大多数RDBMS)。

  • 我想补充一点,作为附带好处,使用 CTE 通常对人类也更具可读性。 (4认同)

Joe*_*ish 10

其他答案很好地涵盖了语法差异,所以我不会深入讨论。相反,这个答案将只涵盖 Oracle 中的性能。

Oracle 优化器可以选择将 CTE 的结果具体化到内部临时表中。它使用启发式方法来执行此操作,而不是基于成本的优化。启发式类似于“如果 CTE 不是一个简单的表达式并且 CTE 在查询中被多次引用,则将其物化”。有一些查询的实现将提高性能。有一些查询的实现会显着降低性能。下面的例子有点做作,但它很好地说明了这一点:

首先创建一个主键包含 1 到 10000 整数的表:

CREATE TABLE N_10000 (NUM_ID INTEGER NOT NULL, PRIMARY KEY (NUM_ID));

INSERT /*+APPEND */ INTO N_10000
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= 10000
ORDER BY LEVEL;

COMMIT;
Run Code Online (Sandbox Code Playgroud)

考虑以下使用两个派生表的查询:

SELECT t1.NUM_ID
FROM 
(
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
) t1
LEFT OUTER JOIN 
(
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
) t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;
Run Code Online (Sandbox Code Playgroud)

我们可以查看此查询并快速确定它不会返回任何行。Oracle 也应该能够使用索引来确定这一点。在我的机器上,查询几乎立即完成,执行以下计划:

好计划

我不喜欢重复自己,所以让我们用 CTE 尝试相同的查询:

WITH N_10000_CTE AS (
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
)
SELECT t1.NUM_ID
FROM N_10000_CTE t1
LEFT JOIN N_10000_CTE t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;
Run Code Online (Sandbox Code Playgroud)

这是计划:

糟糕的计划

这真是一个糟糕的计划。Oracle 没有使用索引,而是将 10000 X 10000 = 100000000 行具体化到临时表中,最终只返回 0 行。该计划的成本约为 6 M,远高于其他查询。在我的机器上完成查询需要 68 秒。

请注意,如果临时表空间中没有足够的内存或可用空间,则查询可能会失败。

我可以使用未记录的INLINE提示来禁止优化器实现 CTE:

WITH N_10000_CTE AS (
  SELECT /*+ INLINE */ n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
)
SELECT t1.NUM_ID
FROM N_10000_CTE t1
LEFT JOIN N_10000_CTE t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;
Run Code Online (Sandbox Code Playgroud)

该查询能够使用索引并几乎立即完成。查询的成本与之前相同,11。因此,对于第二个查询,Oracle 使用的启发式方法导致它选择估计成本为 6 M 的查询,而不是估计成本为 11 的查询。