Voltar para a lista de artigos Artigos
13 minutos de leitura

O que é um CTE em T-SQL? Um guia detalhado para iniciantes com 7 exemplos

Uma expressão de tabela comum (CTE) é um recurso poderoso do T-SQL que simplifica a criação de consultas no SQL Server. As CTEs funcionam como tabelas virtuais (com registros e colunas) que são criadas em tempo real durante a execução de uma consulta. Eles são consumidos pela consulta e destruídos após a execução da consulta.

Em alguns casos - como quando a consulta espera dados em um formato específico e as tabelas de origem têm os dados em outro formato - um CTE pode atuar como uma ponte para transformar os dados nas tabelas de origem no formato esperado pela consulta.

Neste artigo, abordaremos como você pode criar um CTE em T-SQL, o dialeto SQL usado pelo Microsoft SQL Server. Para saber ainda mais sobre CTEs em T-SQL, recomendo nosso curso interativo Consultas Recursivas no MS SQL Server. Ele contém mais de 100 exercícios práticos sobre CTEs em T-SQL. Você praticará tudo o que foi abordado neste artigo por conta própria!

Expressões comuns de tabela (CTEs) em T-SQL

Durante a execução de uma consulta, você pode consultar o CTE como faria com qualquer tabela normal. Ele pode ser usado nas declarações SELECT, INSERT, UPDATE e DELETE. Os CTEs não faziam parte da linguagem SQL original. Eles foram introduzidos como um novo recurso na definição do padrão SQL em 1999 (SQL 3). Em 2005, eles foram disponibilizados no SQL Server.

Os exemplos de consultas SQL mostrados neste artigo serão baseados na tabela olympic_games. A tabela contém os resultados de todos os jogos olímpicos, incluindo 3 registros (ouro, prata e bronze) para cada esporte.

medal_idcityyearmedal_typewinner_namecountrysport
100Seoul1988GoldJohn DivKenyaMarathon Men
101Atlanta1996GoldKulus NamuKenya100 meters race
102Atlanta1996GoldPierre IzyFranceMarathon Men
103Barcelona1992GoldCarlos JerezSpain100 meters race
104Barcelona1992BronzePierre IzyFranceMarathon Men
105Atlanta1996SilverKulus NamuKenya100 meters race
106Barcelona1992GoldKulus NamuKenyaMarathon Men
107Barcelona1992GoldNala YeiEthiopiaMarathon Women
108Los Angeles1984GoldCarlos JerezSpain100 meters race
109Atlanta1996SilverJohn DivKenyaMarathon Men
110Barcelona1992SilverJean FlerFranceMarathon Men

Exemplo 1: um CTE simples

Para explicar a sintaxe de CTE em T-SQL, vamos escrever uma consulta simples. Suponha que queiramos um relatório com os países e o número de vezes que cada país ganhou uma medalha de ouro em uma maratona. Podemos criar primeiro um CTE chamado gold_in_marathon para retornar essas linhas de medalhas de ouro em maratonas masculinas e femininas. No CTE gold_in_marathon, precisamos apenas das colunas city, year e country.

Observe que todos os CTEs começam com a palavra-chave WITH, seguida de parênteses. Entre parênteses, você escreve a consulta que deseja que o CTE retorne. Abaixo está a consulta completa. Com o CTE mostrado em negrito:

WITH gold_in_marathon AS
(
  SELECT 
    city, 
    year, 
    country
  FROM olympic_games
  WHERE medal_type = 'Gold' 
  AND sport IN ('Marathon Men', 'Marathon Women')
)
SELECT 
  country, 
  count(*) AS gold_medals_in_marathon 
FROM gold_in_marathon
GROUP BY country
ORDER BY gold_medals_in_marathon DESC;

Na consulta T-SQL acima, você pode identificar duas consultas diferentes. A primeira é definida pela cláusula WITH entre parênteses: essa é a consulta que define o conteúdo do CTE. Essa é uma consulta SQL normal: você pode inserir todos os diferentes recursos SQL (WHERE, GROUP BY, HAVING, UNION, etc.). O banco de dados usa o resultado para criar uma tabela virtual chamada gold_in_marathon. Você pode ver o nome do CTE (gold_in_marathon) após a cláusula WITH.

A segunda consulta é a consulta externa. Ela faz referência ao CTE gold_in_marathon como qualquer outra tabela. Quando a execução da consulta externa estiver concluída, o CTE gold_in_marathon será destruído e você não poderá mais fazer referência a ele.

Observe que o CTE tem apenas as colunas city, year e country da tabela olympic_games. Depois de criar o CTE, o banco de dados executa a consulta externa, que, por sua vez, lê o CTE gold_in_marathon, agrupando as linhas por country e usando a função COUNT() para obter quantas medalhas de ouro cada país tem. Abaixo estão os resultados da consulta:

countrygold _medals_in_marathon
Kenya2
Ethiopia1
France1

Exemplo 2: uso de CTEs com colunas renomeadas (com alias) em T-SQL

Na próxima consulta, renomearemos explicitamente uma coluna no CTE usando um alias. Vamos supor que queremos um relatório com uma classificação de países para resultados de maratona (mulheres e homens). Cada atleta receberá 3 pontos por cada medalha de ouro, 2 pontos por cada medalha de prata e 1 ponto por cada medalha de bronze. Criaremos um CTE chamado player_points para calcular os pontos de cada jogador. Como usaremos uma coluna calculada para os pontos, precisaremos atribuir um nome a essa coluna usando um alias. Vamos ver a consulta completa:

WITH player_points AS
(
  SELECT 
    country,
    winner_name, 
    SUM(
      CASE medal_type 
        WHEN 'Gold' THEN 3
	  WHEN 'Silver' THEN 2
	  WHEN 'Bronze' THEN 1
	END
	)  AS player_total
    FROM   olympic_games
    WHERE sport in ('Marathon Men', 'Marathon Women')
    GROUP BY country,winner_name
)
SELECT 
  country, 
  SUM(player_total) AS country_points 
FROM player_points
GROUP BY country
ORDER BY country_points DESC;

No CTE player_points, calculamos os pontos de cada jogador usando uma coluna calculada, que não tem um nome. Precisamos definir um nome para essa coluna a fim de podermos nos referir a ela na consulta externa.

Uma maneira de definir um nome é usar um alias (mais tarde veremos outra maneira) usando a cláusula AS. Você pode ver que a coluna foi nomeada player_total na definição do CTE. Na consulta externa, agrupamos as linhas por país para calcular o total de pontos de cada país. Observe que usamos a expressão SUM(player_total) e novamente usamos um alias para renomear a coluna para country_points. Os resultados da consulta estão abaixo:

countryall_medals
Kenya8
France6
Ethiopia3

Antes de encerrar esta seção, gostaria de sugerir o artigo What Is a CTE in SQL Server?, no qual é possível encontrar muitos exemplos de consultas que usam CTEs em T-SQL. O artigo What Is a Common Table Expression (CTE) in SQL? aborda o tópico de CTEs no SQL padrão; ambos os artigos são um bom complemento para este. Se você quiser um curso sobre CTEs e consultas recursivas, sugiro novamente Consultas Recursivas no MS SQL Server.

Exemplo 3: Uso de vários CTEs na mesma consulta T-SQL

Nesta seção, mostraremos dois exemplos de consultas que usam mais de um CTE. No primeiro exemplo, usaremos dois CTEs independentes e a consulta principal acessará ambos.

Vamos supor que queremos um relatório com os nomes dos atletas olímpicos que ganharam pelo menos uma medalha de ouro e uma de prata. O primeiro CTE chama-se gold. Após o nome do CTE, você pode ver os nomes das colunas (winner_name e gold_medals) explicitamente definidos entre parênteses. Essa é a outra maneira de renomear uma coluna no CTE.

O nome do segundo CTE é silver e ele tem duas colunas: winner_name e silver_medals. Observe que não colocamos a cláusula WITH antes do segundo CTE. A cláusula WITH é usada apenas uma vez antes da definição do primeiro CTE. Se tivermos mais CTEs para definir, precisaremos apenas de uma vírgula antes de iniciar a definição do(s) próximo(s) CTE(s).

WITH gold(winner_name,gold_medals) AS
(
  SELECT 
    winner_name, 
    count(*)
  FROM olympic_games
  WHERE medal_type = 'Gold'
  GROUP BY winner_name
), 
silver(winner_name,silver_medals) AS
(
  SELECT 
    winner_name,
    count(*)
  FROM olympic_games
  WHERE medal_type = 'Silver'
  GROUP BY winner_name
)
SELECT  
  g.winner_name, 
  g.gold_medals, 
  s.silver_medals
FROM gold g 
JOIN silver s 
ON g.winner_name = s.winner_name;

Na consulta T-SQL anterior, criamos dois CTEs: gold e silver com o winner_name e a quantidade de medalhas (ouro ou prata) conquistadas por cada jogador. Em seguida, na consulta principal, juntamos os dois CTEs como se fossem tabelas regulares usando uma cláusula JOIN. Como o JOIN sem palavras-chave funciona como um INNER JOIN, somente os registros para o mesmo winner_name em ambas as tabelas serão mostrados no resultado da consulta. A saída é mostrada abaixo:

winner_namegold_medalssilver_medals
John Div11
Kulus Namu21

Exemplo 4: Uma consulta do SQL Server com um CTE baseado em outro CTE

Em seguida, criaremos dois CTEs em uma consulta; no entanto, o segundo CTE será baseado no primeiro CTE. Suponha que queiramos que uma consulta obtenha os 3 principais países em relação à quantidade de medalhas conquistadas em um jogo olímpico. Não queremos repetir países, portanto, se a primeira e a segunda posições forem para o mesmo país, queremos mostrar esse país apenas uma vez. A consulta será a seguinte:

WITH medals AS
(
  SELECT 
    city, 
    year, 
    country, 
    COUNT(*) AS quantity_of_medals
  FROM olympic_games
  GROUP BY city, year, country
), 
country_best_game AS
(
  SELECT  
    country, 
    city, 
    year, 
    quantity_of_medals 
  FROM medals m1
  WHERE quantity_of_medals = ( 
    SELECT max(quantity_of_medals) 
    FROM medals m2
    WHERE  m1.country = m2.country
  )
)
SELECT TOP 3 country, 
  city, 
  year, 
  quantity_of_medals 
FROM country_best_game 
ORDER BY quantity_of_medals DESC;

Nessa consulta, criamos um CTE chamado medals com a quantidade de medalhas obtidas por cada país em cada jogo olímpico em que o país ganhou pelo menos uma medalha. Depois disso, calcularemos um segundo CTE chamado country_best_game (com base no primeiro CTE); ele tem uma linha para cada país com o número máximo de medalhas obtidas em um único jogo. Por fim, na consulta principal, selecionamos apenas os três países com o maior número de medalhas conquistadas em um único jogo. Os resultados da consulta estão abaixo:

countrycityyearquantity_of_medals
KenyaAtlanta19963
FranceBarcelona19922
EthiopiaBarcelona19921

Neste ponto, gostaria de sugerir o artigo How to Learn SQL Common Table Expressions (Como aprender expressões de tabela comuns do SQL), onde você pode ler sobre diferentes abordagens para aprender CTEs. Você também verá vários exemplos de consultas usando CTEs no SQL padrão.

Exemplo 5: uso de um CTE em um INSERT T-SQL

No T-SQL, os CTEs também podem ser usados nas instruções UPDATE, INSERT e DELETE. Como regra geral, qualquer comando SQL que permita um SELECT incorporado (por exemplo, uma instrução CREATE VIEW ) pode suportar um CTE. Vejamos um exemplo de um INSERT usando uma expressão de tabela comum em T-SQL.

Vamos supor que tenhamos uma tabela chamada country_medals_by_game com as colunas country, city, game, number_of_medals, e delta_with_previous_game. O conteúdo de cada coluna é claro, exceto pela coluna delta_with_previous_game. No entanto, ainda não usaremos essa coluna, por isso a explicaremos mais tarde. O INSERT para preencher a tabela é o seguinte:

WITH medals AS
(
  SELECT 
    city, 
    year, 
    country, 
    COUNT(*) AS medals_won
  FROM olympic_games
  GROUP BY city, year, country
)
INSERT INTO country_medals_by_game(city,year,country,quantity_of_medals)
SELECT city, year, country, medals_won
FROM medals;

No INSERT anterior, usamos um CTE chamado medals. Observe que o INSERT começa com a definição do CTE (você pode definir vários CTEs, se necessário). Depois que o CTE é definido, a instrução regular INSERT é iniciada. Neste exemplo, o INSERT usa uma instrução SELECT, que, por sua vez, acessa o CTE medals definido anteriormente.

Exemplo 6: Uso de um CTE em um SQL Server UPDATE

A coluna delta_with_previous_game armazena a diferença de medalhas conquistadas por um país em dois jogos olímpicos consecutivos. Se o país aumentou o número de medalhas conquistadas, essa coluna terá a diferença no número de medalhas como um valor positivo. Se o país ganhou menos medalhas do que no jogo olímpico anterior, a coluna terá um valor negativo. O UPDATE para preencher a coluna é:

WITH medals AS
(
  SELECT 
    city, 
    year, 
    country, 
    COUNT(*) AS medals_won
  FROM olympic_games
  GROUP BY city, year, country
)
UPDATE country_medals_by_game 
SET delta_with_previous_game = (
 SELECT COALESCE(quantity_of_medals - COALESCE(prevgame.medals_won,0),0)
 FROM medals prevgame
 WHERE country_medals_by_game.year = prevgame.year + 4
  AND country_medals_by_game.country = prevgame.country
);

Neste UPDATE, começamos com uma cláusula WITH para definir o mesmo medals CTE que estamos usando. Quando a seção de definição do CTE termina, a instrução UPDATE começa. Na cláusula SET, usamos uma subconsulta para calcular a diferença de medalhas conquistadas em dois jogos olímpicos consecutivos. Observe que a subconsulta acessa o CTE medals e a condição ...

country_medals_by_game.year = prevgame.year + 4

... é fazer a correspondência de uma linha em country_medals_by_game com a linha de medalhas do jogo olímpico anterior (que ocorreu quatro anos antes). Um aspecto interessante a ser observado é o seguinte: Para os países que não participaram de dois jogos contíguos, definimos a coluna delta_with_previous_game como NULL. Isso indica que não podemos calcular a diferença; usar um zero para essa coluna é incorreto porque significaria que o país participou do jogo anterior, o que não aconteceu.

Exemplo 7: Uso de CTEs para Consultas Recursivas em T-SQL

No SQL Server, é comum ter tabelas que representam hierarquias de dados (como funcionário-gerente, parte-subparte ou pai-filho). Para percorrer essas hierarquias em qualquer direção - de cima para baixo ou de baixo para cima - o SQL Server usa uma construção chamada CTEs recursivos.

Para ter uma hierarquia de dados em nosso banco de dados Olympic, adicionaremos um par de colunas à tabela olympic_games. Vamos supor que queiramos identificar as medalhas que representam um recorde mundial. Podemos adicionar uma coluna de texto chamada record e defini-la com true quando uma medalha for associada a um novo recorde mundial.

Além disso, sabemos que todo recorde quebra um recorde anterior, portanto, adicionaremos outra coluna chamada previous_record_medal onde colocaremos o medal_id do recorde anterior. Agora temos uma hierarquia de dados para ler com uma consulta recursiva; vamos mostrar uma visualização parcial da tabela olympic_games com suas novas colunas:

medal_idcityyearmedal_typesportrecordprevious_record_medal
100Seoul1988GoldMarathon MenfalseNULL
101Atlanta1996Gold100 meters racetrue103
102Atlanta1996GoldMarathon Mentrue106
103Barcelona1992Gold100 meters racefalse108
104Barcelona1992BronzeMarathon MenfalseNULL
105Atlanta1996Silver100 meters racefalseNULL
106Barcelona1992GoldMarathon Menfalse100
107Barcelona1992GoldMarathon WomenfalseNULL
108Los Angeles1984Gold100 meters racefalseNULL
109Atlanta1996SilverMarathon MenfalseNULL
110Barcelona1992SilverMarathon MenfalseNULL

Digamos que queiramos obter um relatório com a lista de recordes mundiais na maratona masculina. Podemos começar mostrando o recorde mundial atual e, em seguida, o recorde imediatamente anterior, e assim por diante. Precisaremos de um CTE recursivo para percorrer a hierarquia dos recordes da maratona masculina. Ele terá a seguinte aparência:

WITH record_history (medal_id, year, winner, country, prev_record_medal_id) AS
(
  SELECT 
    medal_id, 
    year, 
    winner_name, 
    country, 
    previous_record_medal
  FROM olympic_games
  WHERE sport = 'Marathon Men' AND record = 'true'

  UNION ALL

  SELECT 
    og.medal_id,
    og.year,
    og.winner_name,
    og.country, 
    og.previous_record_medal
  FROM olympic_games og 
  JOIN record_history mrh 
  ON og.medal_id = mrh.prev_record_medal_id
)
SELECT * 
FROM record_history;

O CTE record_history é obtido como resultado de um UNION ALL. A primeira consulta no UNION é para obter o recorde mundial atual; observe a condição record = true. Após o UNION ALL, temos outra consulta que obtém todos os recordes anteriores da maratona masculina. A chave para conectar uma linha de registro de medalha com a linha de registro de medalha anterior é a condição:

og.medal_id = mrh.prev_record_medal_id

Os resultados da consulta estão abaixo:

medal_idyearwinner_namecountryprevious_record_medal
1021996Pierre IzyFrance106
1061992Kulus NamuKenya100
1001998John DivKenyaNULL

Para evitar um loop infinito em um CTE recursivo, há um limite para o número de invocações permitidas. No SQL Server, esse limite é definido por padrão como 100. No entanto, você pode alterar esse limite usando o parâmetro MAXRECURSION no final da consulta recursiva.

Se você quiser se aprofundar mais nas consultas recursivas, sugiro os artigos How to Write a Recursive CTE in SQL Server e Do it in SQL: Recursive SQL Tree Traversal. Você encontrará vários exemplos e abordagens diferentes para explicar as consultas recursivas.

Os CTEs são um recurso poderoso da linguagem T-SQL

Neste artigo, abordamos como usar CTEs T-SQL para simplificar consultas complexas para bancos de dados do SQL Server. Antes de encerrar, gostaria de sugerir alguns artigos relacionados ao SQL Server. O primeiro é Top 5 SQL CTE Interview Questions, no qual você pode encontrar dicas sobre como gerenciar uma entrevista para um emprego na área de SQL. Outro artigo interessante é How to Install Microsoft SQL Server 2019 and SQL Server Management Studio, onde você pode encontrar ajuda para instalar o SQL Server.

Por fim, gostaria de incentivá-lo a conferir o curso Consultas Recursivas no MS SQL Server. Você pode aprender como processar árvores e gráficos no T-SQL e como organizar suas consultas de forma eficaz. Desenvolva suas habilidades e aumente seus ativos!