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


9

内联视图使您可以从子查询中进行选择,就好像它是另一个表一样:

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

我已经看到了使用不同术语来指代的内容:内联视图,WITH子句,CTE和派生表。在我看来,对于同一件事,它们是不同的供应商特定语法。

这是一个错误的假设吗?两者之间在技术/性能上有什么区别吗?


5
标准SQL中的“正式”名称是派生表(Oracle将其命名为Inline View)和公用表表达式(= WITH...)。您可以将每个派生表都重写为CTE,但不能
反过来

Answers:


8

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

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

MySQL(至少是最新的MariaDB版本)可以实现派生表(甚至添加索引)。
ypercubeᵀᴹ

3
我要补充一点,作为附带的好处,使用CTE通常对人类也更易读。
Joishi Bodio

@JoishiBodio:就我个人而言,我同意你的观点,但是可读性是相当主观的。我宁愿避免提及它
-a1ex07

另外,CTE可以引用先前声明的CTE。除非LATERAL使用派生表,否则无法在同一级别引用先前声明的派生表。
Lennart

8

其他答案很好地涵盖了语法差异,因此我不再赘述。取而代之的是,该答案仅涉及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;

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

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;

我们可以查看此查询并快速确定它不会返回任何行。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;

这是计划:

坏计划

那是一个非常糟糕的计划。Oracle不使用索引,而是将10000 X 10000 = 100000000行具体化到临时表中,最终最终返回0行。该计划的成本约为600万,这比其他查询要高得多。该查询花费了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;

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


1

对于SQL Server,WITH CTE指定临时命名结果集,但仅对于first才需要CTE。即

WITH CTE AS (SELECT .... FROM), 
CTE2 AS (SELECT .... FROM)

SELECT CTE.Column, CTE2.Column
FROM CTE
INNER JOIN CTE2 on CTE.Column = CTE2.Column

但这不是子查询或相关子查询。使用CTE可以执行某些操作,而使用SQL Server中的子查询则可以完成某些操作,例如更新CTE中引用的表。这是使用CTE更新表的示例

子查询将类似于

SELECT
   C1,
   (SELECT C2 FROM SomeTable) as C2
FROM Table

或者,如果要基于a.c1引用/加入/限制结果,则您将在OP中提供相关子查询。

因此,它们绝对不是一回事,尽管在很多情况下,您可以使用这些方法中的一种或多种来获得相同的结果。这仅取决于最终结果是什么。


1

with子句和Oracle中的子查询之间的主要区别是,您可以多次引用该子句中的查询。然后,您可以对其进行一些优化,例如使用materialize提示将其转换为临时表。您也可以通过在with子句中引用自身来进行递归查询。内联视图无法做到这一点。

此处此处可以找到更多信息。


通常,不需要实现提示。默认情况下,Oracle优化程序会决定是否实现CTE是有意义的-但是您可以使用提示MATERIALIZE响应覆盖优化程序评估。INLINE相反。
Wernfried Domscheit,2017年

@WernfriedDomscheit是真的。但是有时优化器不会选择实现CTE,在这种情况下,使用materializehint是有效选项。在优化非常复杂的查询时,有时我需要指定它,因为我知道实现CTE将有利于执行计划。
马尔科Vodopija

0

您需要谨慎使用SQL Server中的CTE,而不仅仅是oracle,在某些情况下,与子查询,交叉应用等相比,使用CTE时查询的执行情况要差得多。

像往常一样,重要的是在各种负载条件下测试任何查询,以确定哪个查询效果最佳。

与使用Oracle的@scsimon相似,有时MS SQL Server在索引使用方面没有达到您的期望。

如果要多次使用同一数据,则CTE可能会更有用;如果仅使用一次,则在大型数据集中,子查询通常会更快。

例如选择*从(我的子查询)加入其他东西...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.