Diferentes Tipos De Família - Diferentes Tipos De Familia Para Colorear
Diferentes Tipos De Familia Para Colorear

Organizar hierarquias sem dor de cabeça

Quem já tentou modelar um organograma de empresa ou uma árvore genealógica num banco relacional sabe que isso não é simples. Você tem pai e filho, recursividade natural, mas SQL é tabular e chato nesse aspecto. A solução que a maioria dos desenvolvedores acaba adotando é o padrão de diferentes tipos de família, popularizado por Joe Celko no livro "Smart Solutions for SQL." A ideia é básica: uma tabela com uma chave estrangeira apontando para ela mesma, formando uma cadeia de referências que representa a hierarquia.

Diferentes tipos de família e suas aplicações práticas

O mais conhecido é o closure table. Funciona assim: além da coluna id e parent_id, você cria uma tabela separada chamada algo como hierarquia_closure, com colunas ancestor e descendant. Cada vez que insere um nó, você propaga todas as suas relações para baixo. Consultar todos os subordinados de uma pessoa vira um JOIN simples, sem subconsultas recursivas que travam seu banco em produção. Eu já vi gente usar apenas parent_id com CTEs recursivas do MySQL 8+. Funciona até certo ponto. Quando a hierarquia atinge mais de quinze níveis, a coisa começa a sangrar. A consulta que antes durava 40 milissegundos passa a levar dois segundos. A closure table resolve isso porque a profundidade não importa — você sempre faz um JOIN direto.

O materialized path é outra variação. Em vez de ponteiros, você armazena o caminho completo como string, tipo "1/4/7/12". Consultar subárvores fica absurdamente rápido com LIKE '1/4/%', mas inserções e movimentos laterais são dolorosos. Você precisa atualizar o caminho de todos os filhos também. Já tive um caso onde mover uma pasta inteira num sistema de documentos atualizou 84 mil linhas porque o caminho mudou. Levou onze minutos e bloqueou tabelas que não deveriam.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Como implementar no dia a dia

Vamos ao que realmente importa. A estrutura básica de closure table com colunas ancestor e descendant é suficiente para a maioria dos casos. Crie uma tabela com id, parent_id, nome e outros campos que fizerem sentido. Depois a tabela declosure com ancestor, descendant e depth. A profundidade serve para saber quantos níveis separa dois nós, o que evita consultas adicionais. A parte crítica é a manutenção. Toda vez que inserir um registro filho, você precisa percorrer todos os ancestrais do pai e criar entradas correspondentes para o novo nó. Em PHP com PDO, algo assim funciona:

$pdo->beginTransaction();
$pdo->exec("INSERT INTO funcionarios (nome, parent_id) VALUES ('Maria Silva', 42)");
$newId = $pdo->lastInsertId();
$pdo->exec("INSERT INTO hierarquia_closure (ancestor, descendant, depth)
SELECT ancestor, $newId, depth + 1 FROM hierarquia_closure WHERE descendant = 42");
$pdo->exec("INSERT INTO hierarquia_closure (ancestor, descendant, depth) VALUES ($newId, $newId, 0)");
$pdo->commit(); Isso parece simples, mas tem uma pegadinha que ninguém menciona nos tutoriais. Se você deletar um nó, não basta remover a linha. Você precisa limpar todas as tuplas na closure table onde aquele nó é ancestor ou descendant. E se o nó tiver filhos, esses filhos herdam o parent do avô. Implementar isso corretamente é mais código do que parece.

Quando não usar esse padrão

Nem tudo são flores. Closure table consome espaço extra. Para uma árvore com dez mil nós e profundidade média de oito níveis, a tabela de closure pode ter entre cinquenta mil e cento e vinte mil linhas, dependendo da distribuição. Materialized path evita esse problema de espaço, mas troca por problemas de manutenção. Os dois têm trade-offs reais. Se a sua hierarquia é estável — isto é, os nós raramente mudam de posição — e você lê muito mais do que escreve, closure table é imbatível. Mas se tem operações de rearranjo constante, considere a nested set model de Mel Niels, que usa left e right values em vez de ponteiros. Ela é rápida para leitura mas pesadíssima para atualizações. Já passei por um projeto onde movimentações semanais de departamentos transformaram queries simples de UPDATE em gargalos de minutos.

Outro cenário onde tudo isso falha é quando a "hierarquia" na verdade é uma rede. Relacionamentos muitos-para-muitos, ciclos, múltiplos pais — aí você abandona o modelo tree e vai para grafos. Neo4j ou até uma tabela de adjacência simples com transações bem escritas vão te dar menos dor de cabeça do que tentar adaptar closure table para algo que não é uma árvore. O ponto é que escolher o tipo de estrutura hierárquica errado no início do projeto custa muito mais do que escolher errado depois. Leitura antecipada do padrão, testes de carga com dados reais e não com cinco linhas de exemplo são o mínimo que qualquer equipe deve fazer antes de commitar a migration. O resto é adaptação conforme os problemas aparecem.