Коли використовувати загальну табличну виразність (CTE)


230

Я почав читати про загальну табличну виразність і не можу придумати випадок використання, коли мені потрібно було б їх використовувати. Вони можуть здатися зайвими, оскільки те ж саме можна зробити з похідними таблицями. Щось мені не вистачає чи добре не розумію? Чи може хтось надати мені простий приклад обмежень за допомогою регулярних запитів вибору, похідних чи тимчасових таблиць, щоб зробити випадок CTE? Будь-які прості приклади були б дуже вдячні.

Відповіді:


197

Один приклад, якщо вам потрібно посилатися / приєднуватися до одного і того ж набору даних кілька разів, ви можете зробити це, визначивши CTE. Тому це може бути формою повторного використання коду.

Приклад самостійного посилання - рекурсія: рекурсивні запити із використанням CTE

Для захоплюючих визначень Microsoft, взятих із Книг Інтернет:

CTE можна використовувати для:

  • Створіть рекурсивний запит. Для отримання додаткової інформації див. Рекурсивні запити, що використовують загальні вирази таблиць.

  • Замініть перегляд, коли загальне використання погляду не потрібно; тобто не потрібно зберігати визначення у метаданих.

  • Увімкнути групування за стовпцем, який походить від скалярного підселекту, або функції, яка або не детермінована, або має зовнішній доступ.

  • Посилайтеся на отриману таблицю кілька разів в одному операторі.


7
Так. Ви не можете самостійно приєднатися до похідної таблиці. Варто зазначити, що самостійне приєднання до CTE все одно залишить вас з двома окремими викликами цього.
Мартін Сміт

@Martin - я здивований. Чи можете ви створити резервну копію цього твердження?
RichardTheKiwi

@John Спасибі, я знаходжу 4guysfromrolla.com/webtech/071906-1.shtml також дуже корисно
imak

4
@cyberkiwi - Який біт? Що самостійне приєднання призведе до двох різних викликів? Дивіться приклад у цій відповіді stackoverflow.com/questions/3362043/…
Мартін Сміт

4
Цікавий факт про CTE. Мені завжди було цікаво, чому NEWID () в CTE змінюється, коли на CTE посилається не один раз. select top 100 * into #tmp from master..spt_values order by 1,2,3,4 select A.number, COUNT(*) from #tmp A inner join #tmp B ON A.number = B.number+1 group by A.numbervswith CTE AS (select top 100 * from master..spt_values order by 1,2,3,4) select A.number, COUNT(*) from CTE A inner join CTE B ON A.number = B.number+1 group by A.number
RichardTheKiwi

50

Я використовую їх для розбиття складних запитів, особливо складних об'єднань та підзапитів. Я вважаю, що я все більше і більше використовую їх як "псевдо-перегляди", щоб допомогти мені осягнути наміри запиту.

Моя єдина скарга на них - це те, що вони не можуть бути використані повторно. Наприклад, у мене може бути збережений додаток з двома операторами оновлення, які можуть використовувати один і той же CTE. Але "сфера" CTE - це лише перший запит.

Проблема в тому, що "прості приклади", ймовірно, не дуже потрібні CTE!

Все-таки дуже зручно.


гаразд. Чи можете ви скласти приклад з якимсь досить складним прикладом, який може допомогти мені обернутися цією концепцією?
імак

28
"Моя єдина скарга на них - це те, що вони не можуть бути використані повторно" - CTE, який ви хочете повторно використовувати, слід вважати кандидатом на отримання VIEW:)
onedaywhen

6
@onedaywhen: Зрозумів, але це означає глобальну сферу, з якою мені не завжди подобається. Іноді в рамках програми я хотів би визначити CTE і використовувати його для вибору та оновлення або вибору подібних даних з різних таблиць.
n8wrl

5
Коли мені потрібен той самий CTE більше, ніж один раз, я подаю його у тимчасову таблицю, а потім використовую тимчасову таблицю скільки завгодно.
Fandango68

43

Я вважаю, що я можу використовувати cte.

Використовувати обчислене значення в пункті де. Мені це здається трохи чистішим, ніж похідна таблиця.

Припустимо, є дві таблиці - запитання та відповіді, об'єднані між собою Ques.ID = Answers.Question_Id (і ідентифікатор вікторини)

WITH CTE AS
(
    Select Question_Text,
           (SELECT Count(*) FROM Answers A WHERE A.Question_ID = Q.ID) AS Number_Of_Answers
    FROM Questions Q
)
SELECT * FROM CTE
WHERE Number_Of_Answers > 0

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

WITH cte AS
(
    SELECT [Quiz_ID] 
      ,[ID] AS Question_Id
      ,null AS Answer_Id
          ,[Question_Text]
          ,null AS Answer
          ,1 AS Is_Question
    FROM [Questions]

    UNION ALL

    SELECT Q.[Quiz_ID]
      ,[Question_ID]
      ,A.[ID] AS  Answer_Id
      ,Q.Question_Text
          ,[Answer]
          ,0 AS Is_Question
        FROM [Answers] A INNER JOIN [Questions] Q ON Q.Quiz_ID = A.Quiz_ID AND Q.Id = A.Question_Id
)
SELECT 
    Quiz_Id,
    Question_Id,
    Is_Question,
    (CASE WHEN Answer IS NULL THEN Question_Text ELSE Answer END) as Name
FROM cte    
GROUP BY Quiz_Id, Question_Id, Answer_id, Question_Text, Answer, Is_Question 
order by Quiz_Id, Question_Id, Is_Question Desc, Name

10
Чи не можна спростити ваш перший приклад, щоб просто використовувати вкладений запит замість CTE?
Сем

2
Обидва приклади можуть бути.
Маначі

3
Вам слід було додати перший без CTE, тоді відразу видно, чому останній корисний.
Уфос

HAVINGще один спосіб зробити фільтр пізнього ступеня, який може бути подібний до використання суб-SELECT
Вільям Ентрікен

21

Один із сценаріїв, який мені здається корисним використовувати CTE, - це коли потрібно отримати DISTINCT рядки даних на основі одного або декількох стовпців, але повернути всі стовпці таблиці. За допомогою стандартного запиту вам, можливо, спочатку доведеться скинути різні значення в темп-таблицю, а потім спробувати приєднати їх до початкової таблиці, щоб отримати решту стовпців, або ви можете написати надзвичайно складний запит розділу, який може повернути результати в один пробіг, але, швидше за все, він буде нечитабельним і спричинить проблеми з продуктивністю.

Але за допомогою CTE (на що відповів Тім Шмелтер у « Вибрати перший екземпляр запису» )

WITH CTE AS(
    SELECT myTable.*
    , RN = ROW_NUMBER()OVER(PARTITION BY patientID ORDER BY ID)
    FROM myTable 
)
SELECT * FROM CTE
WHERE RN = 1

Як бачите, це набагато простіше читати та підтримувати. І порівняно з іншими запитами, це набагато краще за продуктивністю.


16

Можливо, більш доцільним вважати CTE як заміну виду, який використовується для одного запиту. Але це не вимагає накладних даних, метаданих чи стійкості формального перегляду. Дуже корисно, коли вам потрібно:

  • Створіть рекурсивний запит.
  • Використовуйте набір результатів CTE не раз у своєму запиті.
  • Підвищуйте чіткість у вашому запиті, зменшуючи великі шматки однакових запитів.
  • Увімкнути групування за стовпцем, отриманим у наборі результатів CTE

Ось приклад вирізання та вставки, з яким можна грати:

WITH [cte_example] AS (
SELECT 1 AS [myNum], 'a num' as [label]
UNION ALL
SELECT [myNum]+1,[label]
FROM [cte_example]
WHERE [myNum] <=  10
)
SELECT * FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_all' FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_odd' FROM [cte_example] WHERE [myNum] % 2 = 1
UNION
SELECT SUM([myNum]), 'sum_even' FROM [cte_example] WHERE [myNum] % 2 = 0;

Насолоджуйтесь


7

Сьогодні ми дізнаємось про загальний вираз таблиці, що є новою функцією, яка була представлена ​​на SQL сервері 2005 року та доступна в наступних версіях.

Загальна таблична виразність: - Загальний вираз таблиці може бути визначений як тимчасовий набір результатів або іншими словами його замінник поглядів у SQL Server. Загальний вираз таблиці дійсний лише в пакеті оператора, де він був визначений і не може бути використаний в інших сесіях.

Синтаксис оголошення CTE (загальний вираз таблиці): -

with [Name of CTE]
as
(
Body of common table expression
)

Давайте візьмемо приклад: -

CREATE TABLE Employee([EID] [int] IDENTITY(10,5) NOT NULL,[Name] [varchar](50) NULL)

insert into Employee(Name) values('Neeraj')
insert into Employee(Name) values('dheeraj')
insert into Employee(Name) values('shayam')
insert into Employee(Name) values('vikas')
insert into Employee(Name) values('raj')

CREATE TABLE DEPT(EID INT,DEPTNAME VARCHAR(100))
insert into dept values(10,'IT')
insert into dept values(15,'Finance')
insert into dept values(20,'Admin')
insert into dept values(25,'HR')
insert into dept values(10,'Payroll')

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

With CTE_Example(EID,Name,DeptName)
as
(
select Employee.EID,Name,DeptName from Employee 
inner join DEPT on Employee.EID =DEPT.EID
)
select * from CTE_Example

Давайте візьмемо кожен рядок висловлювання по одному і зрозуміємо.

Для визначення CTE ми пишемо "з" пунктом, потім даємо ім'я виразу таблиці, тут я дав ім'я як "CTE_Example"

Тоді ми пишемо "As" і додаємо наш код у дві дужки (---), ми можемо приєднати кілька таблиць у доданих дужках.

В останньому рядку я використав "Select * from CTE_Example", ми маємо на увазі вираз загальної таблиці в останньому рядку коду, тому ми можемо сказати, що це як перегляд, де ми визначаємо і використовуємо подання в єдиному batch і CTE не зберігаються в базі даних як постійний об'єкт. Але він поводиться як погляд. ми можемо виконати операцію видалення та оновлення на CTE, і це матиме прямий вплив на таблицю, на яку посилається, та які використовуються в CTE. Давайте візьмемо приклад, щоб зрозуміти цей факт.

With CTE_Example(EID,DeptName)
as
(
select EID,DeptName from DEPT 
)
delete from CTE_Example where EID=10 and DeptName ='Payroll'

У наведеному вище викладі ми видаляємо рядок із CTE_Example, і він видалить дані з посилальної таблиці "DEPT", яка використовується в CTE.


Я досі не розумію. Яка різниця між цим і просто видаленням з DEPT з точно такою ж умовою? Це, здається, не полегшує нічого.
Хольгер Якобс

Будь ласка, виправте мене, якщо я помиляюся, але план виконання може бути іншим, і я вважаю, що в цьому полягає Неєрай, що існує багато способів досягнення тієї ж мети, але деякі матимуть переваги перед іншими залежно від ситуації. Наприклад, у деяких обставинах може бути простіше прочитати CTE над оператором DELETE FROM, а в інших може бути і зворотне. Продуктивність може поліпшити або погіршити. і т.д.
чудотворець

7

Це дуже корисно, коли ви хочете виконати "замовлене оновлення".

MS SQL не дозволяє використовувати ORDER BY з UPDATE, але за допомогою CTE ви можете це зробити так:

WITH cte AS
(
    SELECT TOP(5000) message_compressed, message, exception_compressed, exception
    FROM logs
    WHERE Id >= 5519694 
    ORDER BY Id
)
UPDATE  cte
SET     message_compressed = COMPRESS(message), exception_compressed = COMPRESS(exception)

Подивіться тут для отримання додаткової інформації: Як оновити та замовити за допомогою ms sql


0

Один момент, на який ще не було вказано, - швидкість . Я знаю, що це давній відповідь на питання, але я думаю, що це заслуговує прямого коментаря / відповіді:

Вони можуть здатися зайвими, оскільки те ж саме можна зробити з похідними таблицями

Коли я використовував CTE в перший раз, я був абсолютно приголомшений його швидкістю. Це було на кшталт підручника, дуже підходящого для CTE, але в усіх випадках, коли я коли-небудь використовував CTE, був значний приріст швидкості. Мій перший запит був складним із отриманими таблицями, на виконання яких потрібні довгі хвилини. З CTE це зайняло частки секунд і залишило мене в шоці, що це навіть можливо.


-4
 ;with cte as
  (
  Select Department, Max(salary) as MaxSalary
  from test
  group by department
  )  
  select t.* from test t join cte c on c.department=t.department 
  where t.salary=c.MaxSalary;

спробуйте це

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