Cuándo usar la expresión de tabla común (CTE)

230

He comenzado a leer sobre la expresión de tabla común y no puedo pensar en un caso de uso en el que deba usarlos. Parecen ser redundantes ya que lo mismo se puede hacer con tablas derivadas. ¿Hay algo que me falta o que no entiendo bien? ¿Alguien puede darme un ejemplo simple de limitaciones con consultas regulares de selección, derivada o tabla temporal para exponer el caso de CTE? Cualquier ejemplo simple sería muy apreciado.

imak
fuente

Respuestas:

197

Un ejemplo, si necesita hacer referencia / unirse al mismo conjunto de datos varias veces, puede hacerlo definiendo un CTE. Por lo tanto, puede ser una forma de reutilización de código.

Un ejemplo de autorreferencia es la recursividad: consultas recursivas con CTE

Para las emocionantes definiciones de Microsoft tomadas de los libros en línea:

Un CTE puede usarse para:

  • Crea una consulta recursiva. Para obtener más información, consulte Consultas recursivas con expresiones de tabla comunes.

  • Sustituya una vista cuando no se requiera el uso general de una vista; es decir, no tiene que almacenar la definición en metadatos.

  • Habilite la agrupación por una columna que se deriva de una subselección escalar, o una función que no es determinista o tiene acceso externo.

  • Consulte la tabla resultante varias veces en la misma declaración.

John Sansom
fuente
77
Sí. No puedes unirte a una tabla derivada. Sin embargo, vale la pena señalar que una autoinscripción en un CTE aún te dejará con 2 invocaciones separadas.
Martin Smith
@ Martin - Estoy sorprendido. ¿Puedes respaldar esa afirmación?
RichardTheKiwi
@John Gracias, encuentro que 4guysfromrolla.com/webtech/071906-1.shtml también es bastante útil
Imak
44
@cyberkiwi - ¿Qué bit? ¿Que una autounión conducirá a 2 invocaciones diferentes? Vea el ejemplo en esta respuesta stackoverflow.com/questions/3362043/…
Martin Smith
44
Dato interesante sobre CTE. Siempre me pregunté por qué NEWID () en el CTE cambia cuando se hace referencia al CTE más de una 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

Los uso para dividir consultas complejas, especialmente combinaciones complejas y subconsultas. Encuentro que los estoy usando cada vez más como 'pseudo-vistas' para ayudarme a entender la intención de la consulta.

Mi única queja sobre ellos es que no se pueden reutilizar. Por ejemplo, puedo tener un proceso almacenado con dos declaraciones de actualización que podrían usar el mismo CTE. Pero el 'alcance' del CTE es solo la primera consulta.

¡El problema es que los 'ejemplos simples' probablemente no necesiten CTE!

Aún así, muy útil.

n8wrl
fuente
Okay. ¿Puede presentar un caso con algún ejemplo relativamente complejo que pueda ayudarme a entender este concepto?
imak
28
"Mi única queja acerca de ellos es que no se pueden reutilizar" - un CTE que se quiera reutilizar se debe considerar candidato para VIEW:) :) un día
cuando el
66
@onedaywhen: Entendido, pero eso implica un alcance global con el que no siempre me siento cómodo. A veces, dentro del alcance de un proceso, me gustaría definir un CTE y usarlo para selecciones y actualizaciones, o selecciones de datos similares de diferentes tablas.
n8wrl
55
Cuando necesito el mismo CTE más de una vez, lo introduzco en una tabla temporal y luego uso la tabla temporal tanto como quiero.
Fandango68
43

Hay dos razones por las que veo usar cte's.

Usar un valor calculado en la cláusula where. Esto me parece un poco más limpio que una tabla derivada.

Supongamos que hay dos tablas: preguntas y respuestas unidas por Questions.ID = Answers.Question_Id (e id. Del cuestionario)

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

Aquí hay otro ejemplo donde quiero obtener una lista de preguntas y respuestas. Quiero que las respuestas se agrupen con las preguntas en los 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
BrianK
fuente
10
¿No puede simplificarse su primer ejemplo simplemente para usar una consulta anidada en lugar del CTE?
Sam
2
Ambos ejemplos podrían ser.
Manachi
3
Debería haber agregado el primero sin el CTE, luego es inmediatamente evidente por qué es útil el último.
Ovnis
HAVINGes otra forma de hacer un filtro de etapa tardía que puede ser similar a usar un sub-SELECT
William Entriken
21

Uno de los escenarios que encontré útiles para usar CTE es cuando desea obtener DISTINCT filas de datos basadas en una o más columnas, pero devuelve todas las columnas de la tabla. Con una consulta estándar, es posible que primero tenga que volcar los valores distintos en una tabla temporal y luego intente unirlos nuevamente a la tabla original para recuperar el resto de las columnas o puede escribir una consulta de partición extremadamente compleja que pueda devolver los resultados en una carrera, pero lo más probable es que sea ilegible y cause problemas de rendimiento.

Sin embargo, usando CTE (como respondida por Tim Schmelter en Seleccionar la primera instancia de un 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 puede ver, esto es mucho más fácil de leer y mantener. Y en comparación con otras consultas, es mucho mejor en rendimiento.

TheDanMan
fuente
16

Quizás sea más significativo pensar en un CTE como un sustituto de una vista utilizada para una sola consulta. Pero no requiere la sobrecarga, los metadatos o la persistencia de una vista formal. Muy útil cuando necesitas:

  • Crea una consulta recursiva.
  • Use el conjunto de resultados del CTE más de una vez en su consulta.
  • Promueva la claridad en su consulta al reducir grandes fragmentos de subconsultas idénticas.
  • Habilite la agrupación por una columna derivada en el conjunto de resultados del CTE

Aquí hay un ejemplo de cortar y pegar para jugar:

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;

Disfrutar

Vic
fuente
7

Hoy vamos a aprender sobre la expresión de tabla común que es una nueva característica que se introdujo en SQL Server 2005 y también está disponible en versiones posteriores.

Expresión de tabla común: la expresión de tabla común se puede definir como un conjunto de resultados temporal o, en otras palabras, es un sustituto de las vistas en SQL Server. La expresión de tabla común solo es válida en el lote de instrucciones donde se definió y no se puede usar en otras sesiones.

Sintaxis de declarar CTE (expresión de tabla común): -

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

Tomemos un ejemplo: -

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

He creado dos tablas de empleado y Departamento e inserté 5 filas en cada tabla. Ahora me gustaría unirme a estas tablas y crear un conjunto de resultados temporal para usarlo más.

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

Tomemos cada línea de la declaración una por una y comprendamos.

Para definir CTE escribimos la cláusula "con", luego le damos un nombre a la expresión de la tabla, aquí he dado el nombre como "CTE_Example"

Luego escribimos "As" y encerramos nuestro código entre dos corchetes (---), podemos unir varias tablas en los corchetes adjuntos.

En la última línea, he usado "Seleccionar * de CTE_Example", estamos refiriendo la expresión de tabla común en la última línea de código, por lo que podemos decir que es como una vista, donde estamos definiendo y usando la vista en una sola batch y CTE no se almacenan en la base de datos como un objeto permanente. Pero se comporta como una vista. podemos realizar una declaración de eliminación y actualización en CTE y eso tendrá un impacto directo en la tabla referenciada que se está utilizando en CTE. Tomemos un ejemplo para entender este hecho.

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

En la declaración anterior, estamos eliminando una fila de CTE_Example y eliminará los datos de la tabla de referencia "DEPT" que se está utilizando en el CTE.

Neeraj Kumar Yadav
fuente
Todavía no entiendo el punto. ¿Cuál es la diferencia entre esto y simplemente eliminar de DEPT con exactamente la misma condición? No parece hacer nada más fácil.
Holger Jakobs
Corríjame si me equivoco, pero el plan de ejecución puede ser diferente, y creo que ese es el punto de Neeraj, que hay muchas formas de lograr el mismo objetivo, pero algunas tendrán ventajas sobre otras dependiendo de la situación. Por ejemplo, puede ser más fácil leer un CTE sobre una declaración DELETE FROM en algunas circunstancias, también lo contrario podría ser cierto en otras. El rendimiento puede mejorar o empeorar. etc.
WonderWorker
7

Es muy útil cuando desea realizar una "actualización ordenada".

MS SQL no le permite usar ORDER BY con UPDATE, pero con la ayuda de CTE puede hacerlo de esa manera:

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)

Mire aquí para obtener más información: Cómo actualizar y ordenar usando ms sql

lado B
fuente
0

Un punto que aún no se ha señalado es la velocidad . Sé que es una vieja pregunta respondida, pero creo que esto merece un comentario / respuesta directa:

Parecen ser redundantes ya que lo mismo se puede hacer con tablas derivadas

Cuando usé CTE la primera vez, me sorprendió por su velocidad. Era un caso similar a un libro de texto, muy adecuado para CTE, pero en todos los casos en que alguna vez usé CTE, hubo una ganancia de velocidad significativa. Mi primera consulta fue compleja con tablas derivadas, la ejecución de largos minutos. Con CTE tomó fracciones de segundos y me sorprendió, que incluso es posible.

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

prueba esto

Sudhir Panda
fuente