Разница между встроенным представлением и предложением 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, но, возможно, не наоборот (например, рекурсивный CTE или многократное использование CTE)
dnoeth

Ответы:

8

Есть некоторые важные различия между встроенными представлениями (производными таблицами) и предложением WITH (CTE) в Oracle. Некоторые из них достаточно универсальны, то есть применимы к другим СУБД.

  1. WITH может быть использован для создания рекурсивных подзапросов, встроенное представление -не (насколько я знаю, то же самое для всех RDBMS, которые поддерживают CTE)
  2. Подзапрос в WITHпредложении, скорее всего, будет физически выполнен первым; во многих случаях выбор между WITHи встроенным представлением заставляет оптимизатор выбирать различные планы выполнения (я полагаю, это зависит от поставщика, возможно, даже от версии).
  3. Подзапрос в WITHможет быть материализован как временная таблица (я не знаю, если какой-либо другой поставщик, но Oracle поддерживает эту функцию).
  4. На подзапрос WITHможно ссылаться несколько раз, в других подзапросах и в основном запросе (верно для большинства СУБД).
a1ex07
источник
MySQL (по крайней мере, последние версии MariaDB) может материализовать производные таблицы (и даже добавлять индексы).
ypercubeᵀᴹ
3
Я хотел бы добавить, что в качестве дополнительного преимущества использование CTE, как правило, более читабельно для людей.
Joishi Bodio
@JoishiBodio: Лично я с тобой согласен, но читабельность довольно субъективная вещь. Я бы предпочел не упоминать об этом
a1ex07
Кроме того, CTE может ссылаться на ранее объявленный CTE. Производная таблица не может ссылаться на ранее объявленную производную таблицу на том же уровне, если LATERALне используется.
Леннарт
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 строк. Стоимость этого плана составляет около 6 млн., Что намного выше, чем другой запрос. На моем компьютере запрос занял 68 секунд.

Обратите внимание, что запрос мог завершиться неудачно, если в табличном пространстве temp недостаточно памяти или свободного места.

Я могу использовать недокументированную 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 М вместо запроса с оценочной стоимостью 11.

Джо Оббиш
источник
1

Для SQL Server WITH CTEуказывает временный именованный набор результатов, но требуется только для первого 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

Или коррелированный подзапрос - это то, что вы указали в своем OP, если вам нужно было ссылаться / объединять / ограничивать свои результаты на основе a.c1.

Таким образом, они определенно не одно и то же, хотя во многих случаях вы можете использовать один или несколько из этих методов для достижения того же результата. Это просто зависит от того, каков этот конечный результат.

scsimon
источник
1

Основное различие между withпредложением и подзапросом в Oracle состоит в том, что вы можете ссылаться на запрос в предложении несколько раз. Затем вы можете выполнить некоторые оптимизации, например, превратив его во временную таблицу, используя materializeподсказку. Вы также можете выполнять с ним рекурсивные запросы, ссылаясь на себя внутри withпредложения. Вы не можете сделать это со встроенным представлением.

Более подробную информацию можно найти здесь и здесь .

Марко Водопия
источник
В общем, материализовывать подсказки не требуется. По умолчанию оптимизатор Oracle решает, имеет ли смысл материализовать CTE или нет, но вы можете перезаписать оценку оптимизатора подсказкой, MATERIALIZEсоответственно. INLINEдля противоположности.
Вернфрид Домшайт
@WernfriedDomscheit это правда. Но иногда оптимизатор не решает материализовать CTE, и в этом случае использование materializeподсказки является допустимым вариантом. Иногда мне приходилось указывать это при оптимизации очень сложных запросов, когда я знал, что материализация CTE принесет пользу плану выполнения.
Марко Водопия
0

Вы должны быть осторожны с CTE в SQL-сервере, а не только с оракулом, в некоторых случаях запросы работают намного хуже при использовании CTE по сравнению с подзапросами, перекрестным применением и т. Д.

Как всегда, важно протестировать любой запрос при различных условиях нагрузки, чтобы определить, какой из них работает лучше всего.

Подобно @scsimon с оракулом, иногда сервер MS SQL не делает то, что вы ожидаете в отношении использования индекса.

Если вы собираетесь использовать одни и те же данные более одного раза, CTE могут быть более полезными, если вы используете их только один раз, часто подзапрос выполняется быстрее в больших наборах данных.

Например, выберите * из (мой подзапрос) присоединиться к чему-то еще ...

Джастин
источник