Voltar para a lista de artigos Artigos
9 minutos de leitura

O que é um CTE Recursivo em SQL?

O artigo que mostrará exemplos práticos do uso de CTEs recursivas em SQL.

Se você já ouviu falar sobre os CTEs recursivos de SQL mas nunca os utilizou, este artigo é para você. É também para você se você nunca se cansa de exemplos de CTE recursivos.

Antes de nos aprofundarmos na recursividade, vou lhe lembrar o que são CTEs e qual é sua sintaxe. Depois farei o mesmo para os CTEs recursivos. Depois disso, mostrarei a você como os CTEs recursivos funcionam em três exemplos.

O que são CTEs?

O CTE (expressão comum de tabela), também conhecido como cláusula WITH, é um recurso SQL que retorna um conjunto de dados temporário que pode ser usado por outra consulta. Como é um resultado temporário, não é armazenado em nenhum lugar, mas ainda assim pode ser referenciado como se você referisse qualquer outra tabela.

Existem dois tipos de CTEs, não-recorrentes e recursivas.

Aqui está um belo artigo que lhe mostrará o que são CTEs e como eles funcionam.

Sintaxe não-recursiva dos CTE

A sintaxe geral de um CTE não-recorrente se parece com esta:

WITH cte_name AS (cte_query_definition)

SELECT *
FROM   cte_name;

A primeira parte da sintaxe é o CTE. Ela começa com a palavra-chave WITH. Em seguida, você dá um nome ao seu CTE. Depois de seguir essa palavra-chave AS, você pode definir o CTE entre parênteses.

A segunda parte da sintaxe é uma simples declaração SELECT. Ela é escrita imediatamente após o CTE recursivo, sem vírgulas, ponto-e-vírgula ou marcas similares. Como eu disse anteriormente, o CTE é usado em outra consulta como em qualquer outra tabela. Isto é exatamente o que a declaração SELECT faz.

Aqui está o artigo que pode adicionalmente ajudá-lo com a sintaxe do CTE e suas regras. E se você precisar de mais alguns exemplos do CTE, este artigo é para você.

Sintaxe Recursiva do CTE

Um CTE recursivo se refere a si mesmo. Ele retorna o subconjunto de resultados, depois repetidamente (recursivamente) se refere a si mesmo, e pára quando retorna todos os resultados.

A sintaxe de um CTE recursivo não é muito diferente da de um CTE não recursivo:

WITH RECURSIVE cte_name AS (
    cte_query_definition (the anchor member)

    UNION ALL

    cte_query_definition (the recursive member)
)


SELECT *
FROM   cte_name;

Novamente, no início do seu CTE está a cláusula WITH. Entretanto, se você quiser que seu CTE seja recursivo, então após WITH você escreve a palavra-chave RECURSIVE. Depois é business as usual: AS é seguido pelos parênteses com a definição da consulta do CTE. Esta primeira definição de consulta é chamada de membro âncora.

Para conectar o membro âncora com o membro recursivo, você precisa usar o comando UNION ou UNION ALL. O membro recursivo é, obviamente, a parte recursiva do CTE que fará referência ao próprio CTE. Você verá como ele funciona em um exemplo muito em breve.

Os CTEs recursivos são usados principalmente quando você deseja consultar dados hierárquicos ou gráficos. Isto pode ser a estrutura organizacional de uma empresa, uma árvore genealógica, um cardápio de restaurante ou várias rotas entre cidades. Veja estes artigos para entender como os CTEs funcionam com estruturas hierárquicas e como consultar os dados dos gráficos.

Agora que entendemos como funcionam os CTEs recursivos, vejamos alguns exemplos.

Exemplo 1 - Encontrar patrões e nível hierárquico para todos os funcionários

Para este problema, vou usar os dados da tabela employeesque tem as seguintes colunas:

  • id: A identificação do funcionário.
  • first_name: O primeiro nome do funcionário.
  • last_name: Sobrenome do funcionário.
  • boss_id: Identificação do chefe do funcionário.

Aqui está como os dados são:

idfirst_namelast_nameboss_id
1DomenicLeaver5
2ClevelandHewins1
3KakalinaAtherton8
4RoxannaFairlieNULL
5HermieComsty4
6PoohGoss8
7FaulknerChalliss5
8BobbeBlakeway4
9LaureneBurchill1
10AugustaGosdin8

Não é muito complicado. Por exemplo, o chefe de Domenic Leaver é o funcionário com a identificação de 5; isto é Hermie Comsty. O mesmo princípio funciona para todos os outros funcionários, exceto Roxanna Fairlie. Ela não tem patrão; há um valor NULL na coluna boss_id. Podemos concluir que Roxanna é a presidente ou proprietária da empresa.

Vamos agora escrever o CTE recursivo para listar todos os funcionários e seus patrões diretos.

WITH RECURSIVE company_hierarchy AS (
  SELECT	id,
    		first_name,
    		last_name,
    		boss_id,
		0 AS hierarchy_level
  FROM employees
  WHERE boss_id IS NULL

  UNION ALL 
  
  SELECT	e.id,
    		e.first_name,
    		e.last_name,
    		e.boss_id, 
		hierarchy_level + 1
  FROM employees e, company_hierarchy ch
  WHERE e.boss_id = ch.id
)

SELECT   ch.first_name AS employee_first_name,
	   ch.last_name AS employee_last_name,
	   e.first_name AS boss_first_name,
	   e.last_name AS boss_last_name,
	   hierarchy_level
FROM company_hierarchy ch
LEFT JOIN employees e
ON ch.boss_id = e.id
ORDER BY ch.hierarchy_level, ch.boss_id;

O que esta consulta faz? É uma consulta recursiva, portanto, começa com WITH RECURSIVE. O nome do CTE é company_hierarchy. Depois de AS, a definição do CTE está entre parênteses.

A primeira declaração SELECT seleciona todos os employee colunas da tabela onde a coluna boss_id é NULL. Em resumo, selecionará Roxanna Fairlie, pois somente ela tem um valor NULL nessa coluna. Ainda mais curto: estou iniciando a recorrência a partir do topo da estrutura organizacional. Há também uma coluna hierarchy_level com o valor 0. Isso significa que o nível do proprietário/presidente é 0 - eles estão no topo da hierarquia.

Usei o UNION ALL para conectar esta declaração SELECT com a segunda, ou seja, com o membro recursivo. No membro recursivo, estou selecionando todas as colunas da tabela employees e o CTE company_hierarchy onde a coluna boss_id é igual à coluna id. Observe a parte hierarchy_level + 1. Isto significa que, a cada repetição, o CTE adicionará 1 ao nível hierárquico anterior, e o fará até chegar ao final da hierarquia. Observe também que estou tratando este CTE como qualquer outra tabela. Para terminar de definir o CTE, basta fechar os parênteses.

Finalmente, há uma terceira declaração SELECT, fora do CTE. Ela seleciona as colunas que mostrarão os funcionários, os nomes de seus chefes e o nível hierárquico. Os dados são retirados do CTE e da tabela employees. Juntei esses dois com um LEFT JOIN, pois quero todos os dados do CTE - incluindo Roxanna Fairlie, que tem o valor NULL na coluna boss_id. O resultado será mostrado em ordem ascendente: primeiro pelo nível hierárquico, depois pelo ID do chefe. Veja como fica:

employee_first_nameemployee_last_nameboss_first_nameboss_last_namehierarchy_level
RoxannaFairlieNULLNULL0
HermieComstyRoxannaFairlie1
BobbeBlakewayRoxannaFairlie1
DomenicLeaverHermieComsty2
FaulknerChallissHermieComsty2
AugustaGosdinBobbeBlakeway2
PoohGossBobbeBlakeway2
KakalinaAthertonBobbeBlakeway2
LaureneBurchillDomenicLeaver3
ClevelandHewinsDomenicLeaver3

Roxanna Fairlie é a última chefe; você já sabia disso. Há dois funcionários no nível 1. Isto significa que Bobbe Blakeway e Hermie Comsty são os subordinados diretos de Roxanna Fairlie. No nível 2, há funcionários cujos chefes diretos são Bobbe Blakeway e Hermie Comsty. Há também um terceiro nível na hierarquia. Esses são funcionários cujo chefe imediato é Domenic Leaver.

Exemplo 2 - Encontrar o Valor do Investimento pelo Investidor

Neste exemplo, vou usar a tabela investment:

  • id: O ID do investimento.
  • investment_amount: O valor do investimento.

Os dados na tabela são parecidos com estes:

idinvestment_amount
19,705,321.00
25,612,948.60
35,322,146.00

Estes são os valores das três opções de investimento possíveis. Eles serão considerados pelos três investidores, que dividirão o valor total do investimento em partes iguais. Sua tarefa é calcular o montante por investidor dependendo de seu número, ou seja, se um, dois, três ou nenhum investidor investe em cada investimento.

A consulta que resolve este problema é:

WITH RECURSIVE per_investor_amount AS (
	SELECT	0 AS investors_number,
			0.00 AS investment_amount,
			0.00 AS individual_amount
	UNION 

	SELECT	investors_number + 1,
			i.investment_amount,
			i.investment_amount / (investors_number + 1)
	FROM investment i, per_investor_amount pia
	WHERE investors_number << 3
)

SELECT *
FROM per_investor_amount
ORDER BY  investment_amount, investors_number;

Mais uma vez, o CTE começa com WITH RECURSIVE, seguido por seu nome e a definição da consulta. Desta vez, vou usar o membro âncora da consulta recursiva para criar alguns dados. As colunas são investors_number, investment_amount, e individual_amount. Este é o ponto a partir do qual quero que a recursividade comece (da mesma forma que no exemplo anterior, com hierarchy_level = 0).

Depois vem o UNION e o membro recursivo. Esta parte da consulta aumentará a coluna investors_number em um a cada recursividade. Ela fará isso para cada investment_amount. A terceira coluna calculará o valor desse investimento por investidor, dependendo do número de investidores participantes. A recursividade será feita para até três investidores (ou seja, até atingir a condição WHERE investors_number < 3).

Depois disso, vem a simples declaração SELECT que retornará todas as colunas do CTE. E aqui está o resultado:

investors_numberinvestment_amountindividual_amount
00.000.00
15,322,146.005,322,146.00
25,322,146.002,661,073.00
35,322,146.001,774,048.67
15,612,948.605,612,948.60
25,612,948.602,806,474.30
35,612,948.601,870,982.87
19,705,321.009,705,321.00
29,705,321.004,852,660.50
39,705,321.003,235,107.00

Não é difícil de analisar. Se não houver investidores, o valor do investimento é zero, assim como o valor individual. Se o investimento for 5.322.146,00 e houver apenas um investidor, então o valor por investidor será 5.322.146,00. Se houver dois investidores no mesmo valor, cada um deles terá que pagar 2.661.073,00. Se os três investidores decidirem investir, cada um pagará 1.774.048,67. Os outros dois valores de investimento seguem o mesmo padrão, como você pode ver na tabela.

Exemplo 3 - Encontrando Rotas entre as Cidades

No terceiro exemplo, vou usar a tabela cities_routeque contém dados sobre as cidades holandesas:

  • city_from: A cidade de partida.
  • city_to: A cidade de destino.
  • distance: A distância entre duas cidades, em quilômetros.
city_fromcity_todistance
GroningenHeerenveen61.4
GroningenHarlingen91.6
HarlingenWieringerwerf52.3
WieringerwerfHoorn26.5
HoornAmsterdam46.1
AmsterdamHaarlem30
HeerenveenLelystad74
LelystadAmsterdam57.2

Use esta tabela para encontrar todas as rotas possíveis de Groningen a Haarlem, mostrando as cidades na rota e a distância total.

Aqui está a consulta para resolver este problema:

WITH RECURSIVE possible_route AS (
	SELECT	cr.city_to,
       		cr.city_from || '->' ||cr.city_to AS route,
       		cr.distance
      FROM cities_route cr
      WHERE cr.city_from = 'Groningen'

UNION ALL

SELECT 	cr.city_to,
       		pr.route || '->' || cr.city_to AS route,
        		CAST((pr.distance + cr.distance) AS DECIMAL(10, 2))
      FROM possible_route pr
INNER JOIN cities_route cr
      		ON cr.city_from = pr.city_to
)

SELECT 	pr.route,
		pr.distance
FROM possible_route pr
WHERE pr.city_to = 'Haarlem'
ORDER BY pr.distance;

Vamos ver o que esta consulta faz. A primeira declaração SELECT na definição do CTE selecionará as colunas da tabela cities_route onde a cidade de partida é Groningen. Note também que há uma nova coluna chamada rota, que vou usar para concatenar as cidades na rota.

A UNION ALL conecta isto com o membro recursivo. Esta declaração SELECT selecionará a cidade de chegada, concatenará as cidades na rota, e finalmente adicionará as distâncias entre estas cidades ao total da rota entre Groningen e Haarlem. Para conseguir tudo isso, juntei-me ao CTE com a tabela cities_route.

Depois vem a declaração SELECT que extrai dados do CTE. Ele selecionará a rota e a distância onde a cidade de chegada é Haarlem, com os dados sendo ordenados por distância em ordem ascendente.

O resultado da consulta é o seguinte:

routedistance
Groningen->Heerenveen->Lelystad->Amsterdam->Haarlem222.6
Groningen->Harlingen->Wieringerwerf->Hoorn->Amsterdam->Haarlem246.5

Não é difícil entender esta tabela. Há duas rotas de Groningen para Haarlem. Elas incluem diferentes cidades no meio e têm 222,6 km e 246,5 km de comprimento, respectivamente.

Se você quiser continuar aprendendo, verifique como você pode usar um CTE recursivo em vez de uma longa consulta SQL. E depois de abordar esse assunto, divirta-se um pouco desenhando algo usando um CTE recursivo.

Continuar praticando CTEs recursivos

Estes três exemplos têm demonstrado as possibilidades de CTEs recursivas em SQL. Agora é hora de construir a partir do que você aprendeu aqui.

Provavelmente, a melhor opção é fazer um curso em nosso Consultas Recursivas. Ele oferece a você muitos exemplos, explicações e oportunidades de prática. O curso é parte da trilha do cursoSQL Avançado , onde você pode aprender sobre outros tópicos avançados de SQL como funções de janela, extensões GROUP BY, e consultas recursivas.

Divirta-se!