Які ефективніші, CTE
чи Temporary Tables
?
Які ефективніші, CTE
чи Temporary Tables
?
Відповіді:
Я б сказав, що це різні поняття, але не надто різні, щоб сказати "крейда і сир".
Тимчасова таблиця хороша для повторного використання або для виконання декількох переходів на обробку набору даних.
CTE можна використовувати або для повторної чи просто покращеної читабельності.
І, як функція перегляду або вбудована таблиця, яка оцінюється, також може розглядатися як макрос, який слід розширити в головному запиті
Тимчасова таблиця - це ще одна таблиця з деякими правилами щодо сфери застосування
Я зберігав програми, де я використовую і обидва (і змінні таблиці)
cte vs temporary tables
так, що IMHO ця відповідь повинна підкреслити недоліки CTE краще. TL; DR пов'язаної відповіді: CTE ніколи не повинен використовуватися для продуктивності. . Я погоджуюся з цією цитатою, коли я пережив недоліки CTE.
Це залежить.
Поперше
Що таке загальний вираз таблиці?
(Нерекурсивний) CTE трактується дуже подібним чином до інших конструкцій, які також можуть бути використані як вираження вбудованих таблиць у SQL Server. Отримані таблиці, перегляди та функції вбудованої таблиці, що оцінюються. Зауважте, що в той час як BOL говорить, що CTE "можна вважати тимчасовим набором результатів", це чисто логічний опис. Частіше за все це не є матеріалізацією саме по собі.
Що таке тимчасовий стіл?
Це набір рядків, що зберігаються на сторінках даних у tempdb. Сторінки даних можуть частково або повністю зберігатися в пам'яті. Крім того, тимчасова таблиця може бути проіндексована і мати статистику стовпців.
Дані тесту
CREATE TABLE T(A INT IDENTITY PRIMARY KEY, B INT , F CHAR(8000) NULL);
INSERT INTO T(B)
SELECT TOP (1000000) 0 + CAST(NEWID() AS BINARY(4))
FROM master..spt_values v1,
master..spt_values v2;
Приклад 1
WITH CTE1 AS
(
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
)
SELECT *
FROM CTE1
WHERE A = 780
Зауважте, що в плані вище немає CTE1. Він просто отримує доступ до базових таблиць безпосередньо і трактується так само, як
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
WHERE A = 780
Переписування шляхом матеріалізації CTE в проміжну тимчасову таблицю тут було б масово контрпродуктивним.
Матеріалізація визначення СТЕ
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
Це передбачало б копіювання близько 8 ГБ даних у тимчасову таблицю, тоді все ще є накладні витрати на вибір.
Приклад 2
WITH CTE2
AS (SELECT *,
ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM T
WHERE B % 100000 = 0)
SELECT *
FROM CTE2 T1
CROSS APPLY (SELECT TOP (1) *
FROM CTE2 T2
WHERE T2.A > T1.A
ORDER BY T2.A) CA
Наведений вище приклад займає на моїй машині близько 4 хвилин.
Тільки 15 рядків з 1 000 000 випадково генерованих значень відповідають предикату, але дороге сканування таблиці відбувається 16 разів, щоб знайти їх.
Це було б хорошим кандидатом для матеріалізації проміжного результату. Еквівалентне перезапис таблиці темп займало 25 секунд.
INSERT INTO #T
SELECT *,
ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM T
WHERE B % 100000 = 0
SELECT *
FROM #T T1
CROSS APPLY (SELECT TOP (1) *
FROM #T T2
WHERE T2.A > T1.A
ORDER BY T2.A) CA
Проміжна матеріалізація частини запиту у тимчасову таблицю іноді може бути корисною, навіть якщо вона оцінюється лише один раз - коли вона дозволяє перекомпілювати решту запитів, скориставшись статистикою про матеріалізований результат. Приклад такого підходу - у статті SQL Cat, коли руйнувати складні запити .
За деяких обставин SQL Server використовує котушку, щоб кешувати проміжний результат, наприклад, CTE, і уникнути необхідності повторної оцінки цього під дерева. Про це йдеться в (перенесеному) елементі Connect. Надайте підказку, щоб примусити проміжну матеріалізацію CTE або отриманих таблиць . Однак щодо цього не створюється ніяких статистичних даних, і навіть якщо кількість спільних рядків повинна сильно відрізнятися від прогнозованої, неможливо, щоб план виконання проекту динамічно адаптувався у відповідь (принаймні, у поточних версіях. Адаптативні плани запитів можуть стати можливими в майбутнє).
CTE має своє використання - коли дані в CTE невеликі і спостерігається сильне поліпшення читабельності, як у випадку з рекурсивними таблицями. Однак його ефективність, безумовно, не краща, ніж табличні змінні, і коли людина має справу з дуже великими таблицями, тимчасові таблиці значно перевершують CTE. Це тому, що ви не можете визначити індекси на CTE і коли у вас є великий об'єм даних, який вимагає з'єднання з іншою таблицею (CTE просто схожий на макрос). Якщо ви приєднуєте кілька таблиць з мільйонами рядків записів у кожній, CTE буде працювати значно гірше, ніж тимчасові таблиці.
Таблиці темп завжди є на диску - так що, поки ваш CTE може зберігатися в пам'яті, це, швидше за все, буде швидше (як і змінна таблиця).
Але знову ж таки, якщо завантаження даних вашої CTE (або змінної темп-таблиці) стає занадто великим, воно також буде збережене на диску, тому великої користі немає.
Як правило, я віддаю перевагу CTE над тимчасовою таблицею, оскільки він минув після того, як я його використав. Мені не потрібно думати про те, щоб явно відмовитись від цього чи чогось.
Отже, жодної чіткої відповіді, врешті-решт, але особисто я вважаю за краще CTE над тимчасовими таблицями.
Тож запит, який мені призначили оптимізувати, був написаний з двома CTE на SQL сервері. Це займало 28 сек.
Я витратив дві хвилини на перетворення їх у темп-таблиці і запит зайняв 3 секунди
Я додав індекс до таблиці темп на полі, до якого він приєднувався, і опустив його на 2 секунди
Три хвилини роботи, і тепер його працює 12 разів швидше, видаляючи CTE. Я особисто не буду використовувати CTE, коли вони будуть жорсткішими для налагодження.
Божевільна річ - це те, що CTE обидва були використані лише один раз, і все-таки ставка індексу виявилася на 50% швидшою.
CTE не займе жодного фізичного місця. Це просто набір результатів, який ми можемо використовувати приєднання.
Тимчасові таблиці є тимчасовими. Ми можемо створювати індекси, обмежує як звичайні таблиці, для цього нам потрібно визначити всі змінні.
Область застосування таблиці темп лише в межах сеансу. EX: Відкрийте два вікна запиту SQL
create table #temp(empid int,empname varchar)
insert into #temp
select 101,'xxx'
select * from #temp
Запустіть цей запит у першому вікні, після чого запустіть запит нижче, у другому вікні ви зможете знайти різницю.
select * from #temp
Я використовував і те і інше, але в масивних складних процедурах завжди знаходив темп-таблиці, які краще працювати і більш методичними. ЦТЕ використовують, але зазвичай мають невеликі дані.
Наприклад, я створив відростки, які повертаються з результатами великих обчислень за 15 секунд, але конвертують цей код для запуску в CTE, і я бачив, що він працює більше 8 хвилин для досягнення тих же результатів.
Пізно на вечірку, але ...
Навколишнє середовище, в якому я працюю, дуже обмежене, підтримуючи деякі продукти постачальників та надаючи послуги з додатковою вартістю, такі як звітність. Через політичні та контрактні обмеження мені зазвичай не дозволено розкіш окремого простору таблиці / даних та / або можливість створювати постійний код [він стає трохи кращим, залежно від програми].
IOW, я зазвичай не можу розробити збережену процедуру, або UDF, або тимчасові таблиці і т. Д. Мені дуже доводиться робити все через інтерфейс мого додатка (Crystal Reports - додавання / посилання таблиць, встановлення де пункти з w / in CR тощо). ). Одне МАЛЕЕ заощадження полягає в тому, що Crystal дозволяє мені використовувати COMMANDS (а також SQL вирази). Деякі речі, які не є ефективними завдяки можливості звичайних таблиць додавання / посилання, можна виконати, визначивши команду SQL. Я використовую CTE через це і даю дуже хороші результати "віддалено". CTE також допомагають обслуговувати звіт / звіт, не вимагаючи розробки коду, передають DBA для компіляції, шифрування, передачі, встановлення, а потім вимагають багаторівневого тестування. Я можу робити CTE через локальний інтерфейс.
Нижньою стороною використання CTE з / п є кожен звіт окремий. Кожен CTE повинен підтримуватися для кожного звіту. Там, де я можу робити SP та UDF, я можу розробити щось, що може використовуватися у кількох звітах, вимагаючи лише посилання на SP та проходження параметрів, як якщо б ви працювали над звичайною таблицею. CR не дуже добре обробляє параметри в SQL-командах, тому цей аспект CR / CTE може бути відсутнім. У цих випадках я зазвичай намагаюся визначити CTE для повернення достатньої кількості даних (але не ВСІХ даних), а потім використовую можливості вибору запису в CR, щоб нарізати та порізати це.
Отже ... моє голосування за CTE (поки я не отримаю простір даних).
Одне використання, де я вважав, що CTE виявив чудову ефективність, коли мені потрібно було приєднати порівняно складний Запит до декількох таблиць, які мали по кілька мільйонів рядків у кожній.
Я використовував CTE, щоб спершу вибрати підмножину на основі індексованих стовпців, щоб спочатку скоротити ці таблиці до кількох тисяч відповідних рядків кожна, а потім приєднав CTE до мого основного запиту. Це експоненціально скоротило час мого запиту.
Хоча результати для CTE не є кешованими, а змінні таблиці, можливо, були б кращим вибором, я дійсно просто хотів їх випробувати і виявив відповідність вищенаведеному сценарію.
Я щойно тестував це - і CTE, і не-CTE (де запит було введено для кожного примірника об'єднання), і тривало ~ 31 секунди. CTE зробив код набагато більш читабельним, проте скоротив його з 241 до 130 рядків, що дуже приємно. З іншого боку, таблиця темпів скоротила її на 132 рядки та запустила П'ять секунд. Без жартів. все це тестування було кешоване - запити виконувались кілька разів раніше.
Зі свого досвіду роботи на SQL сервері я знайшов один із сценаріїв, коли CTE перевершив таблицю темпів
Мені потрібно було використовувати DataSet (~ 100000) зі складного Запиту просто ONCE в моїй збереженій процедурі.
Таблиця темпів викликала накладні витрати на SQL, де моя процедура виконувалась повільно (так як таблиці Temp - це реальні матеріалізовані таблиці, які існують у tempdb та Persist протягом моєї поточної процедури)
З іншого боку, із CTE CTE зберігається лише до наступного запиту. Отже, CTE - це зручна структура пам'яті з обмеженим діапазоном. CTE не використовують tempdb за замовчуванням.
Це один сценарій, коли CTE можуть дійсно допомогти спростити код і перевершити таблицю темпів. Я використовував 2 CTE, щось подібне
WITH CTE1(ID, Name, Display)
AS (SELECT ID,Name,Display from Table1 where <Some Condition>),
CTE2(ID,Name,<col3>) AS (SELECT ID, Name,<> FROM CTE1 INNER JOIN Table2 <Some Condition>)
SELECT CTE2.ID,CTE2.<col3>
FROM CTE2
GO