Різниця між inline view та пунктом З?


9

Вбудовані перегляди дозволяють вибирати з підзапиту так, ніби це була інша таблиця:

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

Я бачив, що це стосувалося використання різних термінів: вбудованих переглядів, пункту З, CTE та похідних таблиць Мені здається, вони є різними синтаксисом, визначеними для постачальника, для однієї і тієї ж речі.

Це неправильне припущення? Чи є якісь технічні / продуктивні відмінності між ними?


5
"Офіційними" іменами у стандартному SQL є похідна таблиця (якій Oracle називає вбудований вигляд ) та загальна таблична виразність (= WITH...). Ви можете переписати кожну отриману таблицю як CTE, але, можливо, не навпаки (наприклад, рекурсивний CTE або використання CTE кілька разів)
dnoeth

Відповіді:


8

Існують деякі важливі відмінності між вбудованими поданнями (похідні таблиці) та пропозицією З (CTE) в Oracle. Деякі з них є досить універсальними, тобто застосовуються до інших RDBMS.

  1. WITH можна використовувати для створення рекурсивних підзапитів, inline view -not (наскільки я знаю, це те саме для всіх RDBMS, які підтримують CTE)
  2. Підзапит у WITHпункті, швидше за все, спочатку виконується фізично; у багатьох випадках вибір між WITHі вбудованим переглядом змушує оптимізатор вибирати різні плани виконання (я думаю, це специфічний для постачальника, можливо, навіть версія).
  3. Підзапит у програмі WITHможе бути матеріалізований як тимчасова таблиця (мені не відомо, чи підтримує цю функцію інший постачальник, окрім Oracle).
  4. На підзапит WITHможна посилатися кілька разів, в інших підзапитах та в головному запиті (вірно для більшості RDBMS).

MySQL (принаймні, останні версії MariaDB) може матеріалізувати отримані таблиці (і навіть додати індекси).
ypercubeᵀᴹ

3
Я хотів би додати, що в якості побічної переваги використання CTE загалом є більш читабельним і для людей.
Джоїші Бодіо

@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 секунд.

Зауважте, що запит міг не вдатися, якщо в області таблиць темп не вистачає пам'яті або вільного місця.

Я можу використовувати незадокументований 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

Або співвіднесений підзапит - це те, що ви вказали у вашій ОП, якщо ви посилалися / приєднувались / обмежували свої результати на основі a.c1.

Отже, вони, безумовно, не є одне і те ж, хоча у багатьох випадках ви можете використовувати один або декілька цих методів для досягнення того ж результату. Це просто залежить від того, який це кінцевий результат.


1

Основна відмінність між withклаузулою та підзапитом в Oracle полягає в тому, що ви можете посилатися на запит у пункті кілька разів. Потім ви можете зробити деякі оптимізації з ним, як перетворити його в темп-таблицю за допомогою materializeпідказки. Ви також можете робити рекурсивні запити з ним, посилаючись на всередині withпропозиції. Ви не можете це зробити з вбудованим видом.

Більше інформації можна знайти тут і тут .


Взагалі матеріалізувати натяк не потрібно. За замовчуванням оптимізатор Oracle вирішує, чи є сенс матеріалізувати CTE чи ні - але ви можете перезаписати оцінку оптимізатора з підказкою MATERIALIZE. INLINEдля протилежного.
Wernfried Domscheit

@WernfriedDomscheit це правда. Але іноді оптимізатор не вирішує реалізувати CTE, і в цьому випадку, використовуючи materializeпідказку, є дійсним варіантом. Мені іноді потрібно було вказати це під час оптимізації дуже складних запитів, де я знав, що матеріалізація CTE принесе користь плану виконання.
Марко Водопія

0

Вам потрібно бути обережним із CTE на сервері SQL, а не просто у оракулі, бувають випадки, коли запити виконуються набагато гірше при використанні CTE у порівнянні з підзапросами, крос-застосуванням тощо

Як завжди, важливо перевірити будь-який запит у різних умовах навантаження, щоб визначити, який із них працює найкраще.

Подібно до @scsimon з oracle, іноді сервер MS SQL не робить того, що ви очікуєте, щодо використання індексу.

Якщо ви збираєтесь використовувати одні й ті самі дані не один раз, CTE можуть бути кориснішими, якщо ви використовуєте їх лише один раз, часто великі набори запитів швидші у великих наборах даних.

наприклад, виберіть * з (мій підзапит) приєднайтеся до чогось іншого ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.