Algumas consultas são naturalmente recursivas: encontrar todos os pré-requisitos de uma disciplina (incluindo pré-requisitos de pré-requisitos), percorrer uma hierarquia de cargos, ou descobrir todos os nós alcançáveis em um grafo. O SQL padrão suporta isso com CTEs recursivas.
Common Table Expressions (WITH)
Antes de introduzir a recursão, revisamos as CTEs — expressões de tabela nomeadas que tornam consultas complexas mais legíveis:
-- CTE simples: disciplinas de semestre par de ENSWITH disciplinas_par AS (SELECT id_disciplina, nome_disciplina, semestreFROM disciplinaWHERE id_curso =311AND semestre % 2=0)SELECT dp.nome_disciplina, dp.semestre,COUNT(pr.id_prereq) AS num_prereqsFROM disciplinas_par dpLEFTJOIN prereq pr ON pr.id_disciplina = dp.id_disciplinaGROUPBY dp.id_disciplina, dp.nome_disciplina, dp.semestreORDERBY dp.semestre, dp.nome_disciplina;
8 records
nome_disciplina
semestre
num_prereqs
Bases da Eng. de Software 2
2
0
Projeto Aplicado 2 - site WEB
2
0
Bases da Eng. de Software 4
4
1
Projeto Aplicado 4 - blockchain
4
1
Bases da Eng. de Software 6
6
2
Projeto Aplicado 6 - Machine Learning
6
1
Bases da Eng. de Software 8
8
1
Projeto Aplicado 8 - Sistema em Tempo Real
8
2
WITH RECURSIVE
Uma CTE recursiva define uma relação em termos de si mesma. A estrutura é sempre:
WITH RECURSIVE nome_cte(colunas) AS (-- Caso base: consulta inicial (não recursiva)SELECT...UNION [ALL]-- Caso recursivo: referencia nome_cteSELECT... FROM... JOIN nome_cte ON...)SELECT... FROM nome_cte;
O SGBD executa a CTE iterativamente: 1. Avalia o caso base → resultado inicial R₀ 2. Avalia o caso recursivo usando R₀ → produz R₁ 3. Avalia usando R₁ → produz R₂ 4. Continua até que o caso recursivo produza um conjunto vazio 5. O resultado final é a união de todos os Rᵢ
UNION elimina duplicatas entre iterações (importante quando o grafo pode ter caminhos múltiplos para o mesmo nó). UNION ALL mantém todas as ocorrências.
Exemplo 1 — Pré-requisitos Transitivos
A tabela prereq registra relações diretas: “a disciplina X requer Y”. Para encontrar todos os pré-requisitos de uma disciplina (incluindo os transitivos), precisamos de recursão.
-- Estrutura de pré-requisitos diretos no bancoSELECT pr.id_disciplina, d1.nome_disciplina AS disciplina, pr.id_prereq, d2.nome_disciplina AS prerequisitoFROM prereq prJOIN disciplina d1 ON d1.id_disciplina = pr.id_disciplinaJOIN disciplina d2 ON d2.id_disciplina = pr.id_prereqORDERBY pr.id_disciplina, pr.id_prereq;
Displaying records 1 - 10
id_disciplina
disciplina
id_prereq
prerequisito
1110401
Microeconomia 2
1110201
História Econômica Geral
1110601
Economia Brasileira
1110401
Microeconomia 2
1110602
Orçamento e Finanças Públicas
1110402
Macroeconomia 2
1110801
Monografia
1110601
Economia Brasileira
1110801
Monografia
1110602
Orçamento e Finanças Públicas
2110401
Eletiva I
2110201
Desenvolvimento Humano
2110601
Eletiva II
2110401
Eletiva I
2110801
Tecnologia Educacional - Design
2110601
Eletiva II
3110401
Bases da Eng. de Software 4
3110201
Bases da Eng. de Software 2
3110402
Projeto Aplicado 4 - blockchain
3110202
Projeto Aplicado 2 - site WEB
-- Todos os pré-requisitos transitivos de cada disciplinaWITH RECURSIVE prereqs_transitivos(id_disciplina_raiz, id_prereq, profundidade) AS (-- Caso base: pré-requisitos diretosSELECT id_disciplina, id_prereq, 1FROM prereqUNION-- Caso recursivo: pré-requisitos dos pré-requisitosSELECT pt.id_disciplina_raiz, p.id_prereq, pt.profundidade +1FROM prereqs_transitivos ptJOIN prereq p ON p.id_disciplina = pt.id_prereq)SELECT pt.id_disciplina_raiz AS disciplina_id, d_raiz.nome_disciplina AS disciplina, pt.id_prereq AS prereq_id, d_prereq.nome_disciplina AS prerequisito, pt.profundidade AS nivelFROM prereqs_transitivos ptJOIN disciplina d_raiz ON d_raiz.id_disciplina = pt.id_disciplina_raizJOIN disciplina d_prereq ON d_prereq.id_disciplina = pt.id_prereqORDERBY disciplina, nivel, prereq_id;
Displaying records 1 - 10
disciplina_id
disciplina
prereq_id
prerequisito
nivel
3110401
Bases da Eng. de Software 4
3110201
Bases da Eng. de Software 2
1
3110601
Bases da Eng. de Software 6
3110401
Bases da Eng. de Software 4
1
3110601
Bases da Eng. de Software 6
3110402
Projeto Aplicado 4 - blockchain
1
3110601
Bases da Eng. de Software 6
3110201
Bases da Eng. de Software 2
2
3110601
Bases da Eng. de Software 6
3110202
Projeto Aplicado 2 - site WEB
2
3110801
Bases da Eng. de Software 8
3110601
Bases da Eng. de Software 6
1
3110801
Bases da Eng. de Software 8
3110401
Bases da Eng. de Software 4
2
3110801
Bases da Eng. de Software 8
3110402
Projeto Aplicado 4 - blockchain
2
3110801
Bases da Eng. de Software 8
3110201
Bases da Eng. de Software 2
3
3110801
Bases da Eng. de Software 8
3110202
Projeto Aplicado 2 - site WEB
3
A coluna nivel indica a distância: 1 = pré-requisito direto; 2 = pré-requisito do pré-requisito; e assim por diante.
Exemplo 2 — Caminho Mínimo: Quantos Semestres de Antecedência?
Combinando a recursão com MIN, podemos responder: “qual a distância máxima na cadeia de pré-requisitos de cada disciplina?”
WITH RECURSIVE prereqs_transitivos(id_disciplina_raiz, id_prereq, profundidade) AS (SELECT id_disciplina, id_prereq, 1FROM prereqUNIONSELECT pt.id_disciplina_raiz, p.id_prereq, pt.profundidade +1FROM prereqs_transitivos ptJOIN prereq p ON p.id_disciplina = pt.id_prereq)SELECT d.nome_disciplina AS disciplina, d.semestre AS semestre_curriculo,COUNT(DISTINCT pt.id_prereq) AS total_prereqs_transitivos,MAX(pt.profundidade) AS profundidade_maximaFROM disciplina dJOIN prereqs_transitivos pt ON pt.id_disciplina_raiz = d.id_disciplinaGROUPBY d.id_disciplina, d.nome_disciplina, d.semestreORDERBY profundidade_maxima DESC, total_prereqs_transitivos DESC;
Displaying records 1 - 10
disciplina
semestre_curriculo
total_prereqs_transitivos
profundidade_maxima
Projeto Aplicado 8 - Sistema em Tempo Real
8
6
3
Monografia
8
5
3
Bases da Eng. de Software 8
8
5
3
Tecnologia Educacional - Design
8
3
3
Bases da Eng. de Software 6
6
4
2
Projeto Aplicado 6 - Machine Learning
6
2
2
Eletiva II
6
2
2
Economia Brasileira
6
2
2
Orçamento e Finanças Públicas
6
1
1
Projeto Aplicado 4 - blockchain
4
1
1
Exemplo 3 — Hierarquia de Estrutura Acadêmica
A estrutura Centro → Escola → Curso → Disciplina é uma hierarquia. Embora tenhamos JOINs diretos para ela, o padrão WITH RECURSIVE funciona igualmente para qualquer árvore onde a relação pai–filho é armazenada em tabela:
-- Hierarquia completa: centro → escola → curso (sem recursão, mas ilustra o padrão)WITH estrutura AS (SELECT ce.nome_centro AS nivel1, es.nome_escola AS nivel2, cu.nome_curso AS nivel3, cu.id_cursoFROM centro ceJOIN escola es ON es.id_centro = ce.id_centroJOIN curso cu ON cu.id_escola = es.id_escola)SELECT e.nivel1, e.nivel2, e.nivel3,COUNT(a.id_aluno) AS alunosFROM estrutura eLEFTJOIN aluno a ON a.id_curso = e.id_cursoGROUPBY e.nivel1, e.nivel2, e.nivel3ORDERBY e.nivel1, e.nivel2, e.nivel3;
3 records
nivel1
nivel2
nivel3
alunos
Centro de Educação, Magistério e Artes
Escola de Educação, Magistério e Arte
Pedagogia
6
Centro de Engenharias, Tecnologia e Inovação
Escola Superior de Engenharias, Tecnologia e Inovação
Engenharia de Software
6
Ciências Humanas, Cidadania e Meio Ambiente
Escola Superior de Gestão
Ciências Econômicas
6
Guardas Contra Recursão Infinita
Aviso
Se o grafo contiver ciclos (A → B → A), a CTE recursiva pode não terminar. Estratégias de proteção:
UNION em vez de UNION ALL: elimina duplicatas entre iterações — se um nó já foi visitado, não é reinserido na próxima iteração.
Contador de profundidade com WHERE profundidade < N: limita o número de iterações.
Array de caminho: acumula os nós visitados e verifica se o próximo já está no array (PostgreSQL específico).
No banco da UnDF, prereq não deveria ter ciclos (uma disciplina não pode ser pré-requisito de si mesma transitivamente), mas em dados reais essa verificação é importante.
-- Exemplo de guarda com profundidade máximaWITH RECURSIVE prereqs_limitados(id_raiz, id_prereq, profundidade) AS (SELECT id_disciplina, id_prereq, 1FROM prereqUNIONSELECT pt.id_raiz, p.id_prereq, pt.profundidade +1FROM prereqs_limitados ptJOIN prereq p ON p.id_disciplina = pt.id_prereqWHERE pt.profundidade <10-- guarda: no máximo 10 níveis)SELECT...
Para Praticar
-- Disciplinas sem nenhum pré-requisito (pontos de entrada do grafo)SELECT d.id_disciplina, d.nome_disciplina, d.semestreFROM disciplina dWHERE d.id_disciplina NOTIN (SELECT id_disciplina FROM prereq)AND d.id_curso =311ORDERBY d.semestre;
2 records
id_disciplina
nome_disciplina
semestre
3110201
Bases da Eng. de Software 2
2
3110202
Projeto Aplicado 2 - site WEB
2
-- Disciplinas "folha": possuem pré-requisitos mas não são pré-requisito de ninguémSELECT d.id_disciplina, d.nome_disciplina, d.semestreFROM disciplina dWHERE d.id_disciplina IN (SELECT id_disciplina FROM prereq) -- tem prereqAND d.id_disciplina NOTIN (SELECT id_prereq FROM prereq) -- não é prereq de ninguémORDERBY d.semestre;
4 records
id_disciplina
nome_disciplina
semestre
1110801
Monografia
8
2110801
Tecnologia Educacional - Design
8
3110801
Bases da Eng. de Software 8
8
3110802
Projeto Aplicado 8 - Sistema em Tempo Real
8
-- Quantos pré-requisitos transitivos tem cada disciplina de ENS?WITH RECURSIVE prereqs_transitivos(id_disciplina_raiz, id_prereq) AS (SELECT id_disciplina, id_prereq FROM prereqUNIONSELECT pt.id_disciplina_raiz, p.id_prereqFROM prereqs_transitivos ptJOIN prereq p ON p.id_disciplina = pt.id_prereq)SELECT d.nome_disciplina, d.semestre,COALESCE(COUNT(DISTINCT pt.id_prereq), 0) AS total_prereqs_transitivosFROM disciplina dLEFTJOIN prereqs_transitivos pt ON pt.id_disciplina_raiz = d.id_disciplinaWHERE d.id_curso =311GROUPBY d.id_disciplina, d.nome_disciplina, d.semestreORDERBY total_prereqs_transitivos DESC, d.semestre;