Quando usar o Common Table Expression (CTE)


230

Comecei a ler sobre Common Table Expression e não consigo pensar em um caso de uso em que precisaria usá-los. Eles parecem redundantes, pois o mesmo pode ser feito com tabelas derivadas. Há algo que estou perdendo ou não entendo bem? Alguém pode me dar um exemplo simples de limitações, com consultas regulares de seleção, derivação ou tabela temporária, no caso da CTE? Quaisquer exemplos simples seriam muito apreciados.

Respostas:


197

Um exemplo, se você precisar fazer referência / ingressar no mesmo conjunto de dados várias vezes, poderá fazê-lo definindo um CTE. Portanto, pode ser uma forma de reutilização de código.

Um exemplo de auto-referência é a recursão: Consultas recursivas usando CTE

Para emocionantes definições da Microsoft, tiradas dos Manuais Online:

Um CTE pode ser usado para:

  • Crie uma consulta recursiva. Para obter mais informações, consulte Consultas recursivas usando expressões comuns de tabela.

  • Substitua uma visão quando o uso geral de uma visão não for necessário; ou seja, você não precisa armazenar a definição nos metadados.

  • Habilite o agrupamento por uma coluna derivada de uma subseleção escalar ou por uma função que não seja determinística ou tenha acesso externo.

  • Referencie a tabela resultante várias vezes na mesma instrução.


7
Sim. Você não pode ingressar em uma tabela derivada. Vale ressaltar que uma auto-junção em um CTE ainda o deixará com duas invocações separadas.
Martin Smith

@ Martin - Estou surpreso. Você pode fazer backup dessa declaração?
precisa saber é o seguinte

Graças @ John, eu estou encontrando 4guysfromrolla.com/webtech/071906-1.shtml bastante útil também
IMAK

4
@cyberkiwi - Qual parte? Que uma auto-junção levará a 2 invocações diferentes? Veja o exemplo nesta resposta stackoverflow.com/questions/3362043/…
Martin Smith

4
Fato interessante sobre CTE. Sempre me perguntei por que NEWID () no CTE muda quando o CTE é referenciado mais de uma vez. 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

Eu os uso para interromper consultas complexas, especialmente junções e subconsultas complexas. Acho que os estou usando cada vez mais como 'pseudo-visualizações' para me ajudar a entender a intenção da consulta.

Minha única reclamação sobre eles é que eles não podem ser reutilizados. Por exemplo, eu posso ter um processo armazenado com duas instruções de atualização que podem usar o mesmo CTE. Mas o 'escopo' do CTE é apenas a primeira consulta.

O problema é que "exemplos simples" provavelmente não precisam realmente de CTE!

Ainda assim, muito útil.


Está bem. Você pode argumentar com um exemplo relativamente complexo que pode me ajudar a entender esse conceito?
IMAK

28
"Minha única reclamação sobre eles é que eles não podem ser reutilizados" - uma CTE que você deseja reutilizar deve ser considerada candidata a um VIEW:)
onedaywhen

6
@onedaywhen: Entendido, mas isso implica um escopo global em que nem sempre estou confortável. Às vezes, no escopo de um processo, eu gostaria de definir um CTE e usá-lo para seleções e atualizações, ou seleciona dados semelhantes de tabelas diferentes.
N8wrl

5
Quando eu preciso do mesmo CTE mais de uma vez, eu o alimento em uma tabela temporária e depois uso a tabela temporária o quanto eu quiser.
Fandango68

43

Existem duas razões pelas quais vejo o uso de cte.

Para usar um valor calculado na cláusula where. Isso me parece um pouco mais limpo do que uma tabela derivada.

Suponha que haja duas tabelas - Perguntas e Respostas unidas por Questions.ID = Answers.Question_Id (e ID do questionário)

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

Aqui está outro exemplo em que quero obter uma lista de perguntas e respostas. Quero que as respostas sejam agrupadas com as perguntas nos resultados.

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
Seu primeiro exemplo não pode ser simplificado para usar apenas uma consulta aninhada em vez da CTE?
Sam

2
Ambos os exemplos podem ser.
Manachi

3
Você deveria ter adicionado o primeiro sem o CTE, e imediatamente fica claro por que o último é útil.
Ufos

HAVINGé outra maneira de criar um filtro de estágio final que pode ser semelhante ao uso de um sub-SELECT
William Entriken

21

Um dos cenários que achei úteis para usar o CTE é quando você deseja obter linhas de dados DISTINCT com base em uma ou mais colunas, mas retorna todas as colunas da tabela. Com uma consulta padrão, você pode primeiro despejar os valores distintos em uma tabela temporária e tentar juntá-los à tabela original para recuperar o restante das colunas ou escrever uma consulta de partição extremamente complexa que possa retornar os resultados em uma execução, mas com maior probabilidade, será ilegível e causará problemas de desempenho.

Porém, usando CTE (conforme respondido por Tim Schmelter em Selecionar a primeira instância de um registro )

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

Como você pode ver, isso é muito mais fácil de ler e manter. E, em comparação com outras consultas, é muito melhor no desempenho.


16

Talvez seja mais significativo pensar em um CTE como um substituto para uma exibição usada para uma única consulta. Mas não requer a sobrecarga, metadados ou persistência de uma exibição formal. Muito útil quando você precisa:

  • Crie uma consulta recursiva.
  • Use o conjunto de resultados do CTE mais de uma vez na sua consulta.
  • Promova a clareza na sua consulta, reduzindo grandes pedaços de subconsultas idênticas.
  • Habilitar o agrupamento por uma coluna derivada no conjunto de resultados do CTE

Aqui está um exemplo de recortar e colar para brincar:

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;

Aproveitar


7

Hoje vamos aprender sobre a expressão de tabela comum, que é um novo recurso que foi introduzido no SQL Server 2005 e disponível também em versões posteriores.

Expressão comum da tabela: - A expressão comum da tabela pode ser definida como um conjunto de resultados temporário ou, em outras palavras, é um substituto dos modos de exibição no SQL Server. A expressão de tabela comum é válida apenas no lote de instruções em que foi definida e não pode ser usada em outras sessões.

Sintaxe da declaração CTE (Expressão comum da tabela): -

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

Vamos dar um exemplo: -

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')

Criei duas tabelas employee e Dept e inseri 5 linhas em cada tabela. Agora eu gostaria de juntar essas tabelas e criar um conjunto de resultados temporário para usá-lo ainda mais.

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

Vamos pegar cada linha da afirmação uma por uma e entender.

Para definir CTE, escrevemos a cláusula "with" e, em seguida, atribuímos um nome à expressão da tabela. Aqui, nomeei como "CTE_Example"

Em seguida, escrevemos "Como" e colocamos nosso código entre dois colchetes (---), podemos juntar várias tabelas entre colchetes.

Na última linha, usei "Select * from CTE_Example", estamos nos referindo à expressão da tabela Common na última linha de código. Assim, podemos dizer que é como uma visualização, onde estamos definindo e usando a visualização em uma única lote e CTE não é armazenado no banco de dados como um objeto permanente. Mas se comporta como uma visão. podemos executar instruções de exclusão e atualização no CTE e isso terá impacto direto na tabela referenciada que está sendo usada no CTE. Vamos dar um exemplo para entender esse fato.

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

Na instrução acima, estamos excluindo uma linha do CTE_Example e os dados da tabela referenciada "DEPT" que estão sendo usados ​​no CTE.


Ainda não entendi o ponto. Qual é a diferença entre isso e apenas excluir o DEPT com exatamente a mesma condição? Parece não facilitar nada.
Holger Jakobs

Por favor, corrija-me se estiver errado, mas o plano de execução pode ser diferente, e acho que é o ponto de Neeraj, que existem muitas maneiras de alcançar o mesmo objetivo, mas algumas terão vantagens sobre outras, dependendo da situação. Por exemplo, pode ser mais fácil ler um CTE do que uma instrução DELETE FROM em algumas circunstâncias, também o inverso pode ser verdadeiro em outras. O desempenho pode melhorar ou piorar. etc
WonderWorker

7

É muito útil quando você deseja executar uma "atualização ordenada".

O MS SQL não permite que você use ORDER BY com UPDATE, mas com a ajuda do CTE, é possível fazer o seguinte:

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)

Procure aqui mais informações: Como atualizar e solicitar usando o ms sql


0

Um ponto ainda não apontado é a velocidade . Sei que é uma pergunta respondida antiga, mas acho que isso merece comentário / resposta direta:

Eles parecem redundantes, pois o mesmo pode ser feito com tabelas derivadas

Quando usei o CTE, fiquei impressionado com a velocidade. Era o caso de um livro didático, muito adequado para o CTE, mas em todas as ocorrências que eu já usei o CTE, houve um ganho de velocidade significativo. Minha primeira consulta foi complexa com tabelas derivadas, demorando muito para ser executada. Com o CTE, levou frações de segundos e me deixou chocado, que é até possível.


-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;

tente isso

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.