Trabalhando com dados hierárquicos – Parte III

Saudações! No capítulo anterior desta série, falei de solicitações comuns relacionadas a dados hierárquicos utilizando o método iterativo. Hoje tratarei das mesmas solicitações através de CTEs recursivas.

CTEs recursivas têm uma estrutura um pouco diferente das outras CTEs. De acordo com o BOL [1], uma CTE recursiva é constituída por três elementos:

1. Chamada da rotina
A primeira chamada da rotina é quando o membro âncora é executado. Por membro âncora, entenda qualquer consulta que não faça referência à própria CTE.

2. Chamada recursiva da rotina
A chamada recursiva inclui uma ou mais consultas unidas pela cláusula UNION ALL que fazem referências à própria CTE. Estas consultas são conhecidas como membros recursivos.

3. Verificação de encerramento
Um processo implícito que ocorre quando a chamada recursiva não retorna mais registros.

Inicialmente esta ideia de recursividade pode parecer complexa, então partirei direto para as solicitações e, através dos exemplos, este conceito ficará mais claro.

SUBORDINADOS – MÉTODO RECURSIVO

Uma grande vantagem do método recursivo sobre o método iterativo é não existir a necessidade de materializar os dados previamente. Desta forma, seguiremos os seguintes passos para elaborar o script:

  1. Criar o membro âncora da CTE para que traga o nó raiz da sub-árvore;
  2. Criar o membro recursivo da CTE para que traga os subordinados da recursão anterior;
  3. Retornar a hierarquia;

Utilizando o exemplo inicial do método iterativo, para trazer as patentes subordinadas à patente de Comandante, adaptado para o método recursivo seria algo como:

USE tempdb;
GO
-- Iniciando CTE
;WITH Subordinados AS (
-- Membro Âncora
SELECT patente_id, 0 AS nivel
FROM dbo.Patentes
WHERE patente_id = 2 -- Comandante

UNION ALL
-- Membro Recursivo
SELECT Filho.patente_id, Pai.nivel + 1 AS nivel
FROM Subordinados AS Pai
JOIN dbo.Patentes AS Filho
ON Pai.patente_id = Filho.patente_superior_id
)
-- Fazendo o JOIN para trazer as patentes
SELECT P.patente_id, P.patente_descricao, S.nivel
FROM Subordinados AS S
JOIN dbo.Patentes AS P
ON S.patente_id = P.patente_id;

A primeira consulta no corpo da CTE – que define a raíz – é o membro âncora, após o UNION ALL vem a consulta recursiva. No membro recursivo, é feito um JOIN da própria CTE com a tabela base em busca dos subordinados. Cada recursão irá para o próximo nível de subordinados até que não existam mais subordinados.

fascinating_spock

De fato, Spock. A recursividade é uma ferramenta muito poderosa e, para mim, é uma das funcionalidades mais intrigantes (ou deveria dizer “fascinantes”?) das CTEs. O resultado do script anterior é exatamente o mesmo que o encontrado através do método iterativo.

recursivo_subordinado_resultado

Note que poderíamos utilizar diretamente a descrição da patente nos membros da CTE para que não fosse necessário realizar o último JOIN. No entanto, estou optando por esta forma para facilitar o entendimento no momento de encapsular o código. Então, aproveitando este código para encapsular a lógica de subordinados numa função com valor de tabela, terei algo como o script a seguir para retornar os tripulantes subordinados ao Sulu.

USE tempdb;
GO
IF OBJECT_ID('dbo.Subordinados') IS NOT NULL
DROP FUNCTION dbo.Subordinados;
GO
CREATE FUNCTION dbo.Subordinados
(@raiz AS INT) RETURNS TABLE
AS
RETURN
-- Iniciando CTE
WITH Subordinados AS (
-- Membro Âncora
SELECT tripulante_id, 0 AS nivel
FROM dbo.Tripulantes 
WHERE tripulante_id = @raiz

UNION ALL
-- Membro Recursivo
SELECT Filho.tripulante_id, Pai.nivel + 1 AS nivel
FROM Subordinados AS Pai
JOIN dbo.Tripulantes AS Filho
ON Pai.tripulante_id = Filho.superior_id
)
SELECT tripulante_id,nivel
FROM Subordinados;

Agora é simples obter os subordinados de um tripulante. Basta fazer o JOIN com a tabela base e voilà. A figura abaixo mostra o resultado para o Tenente-Comandante Sulu.

consulta_recursiva_resultado

SUPERIORES – MÉTODO RECURSIVO

Para encontrar os nós pais de um determinado nó até a raiz da árvore, irei apenas seguir o caminho contrário do método para encontrar os filhos (subordinados). Logo, ao invés de descer nos níveis hierárquicos, vou subir até a raiz. É importante notar que, diferentemente do método iterativo utilizado no capítulo anterior, neste método é possível ter mais de um nó pai. Isso não ocorrerá para esta árvore, mas vale o aviso.

Adaptando o código anterior para trazer os superiores em comando de um determinado tripulante, temos:

USE tempdb;
GO
IF OBJECT_ID('dbo.Superiores') IS NOT NULL
DROP FUNCTION dbo.Superiores;
GO
CREATE FUNCTION dbo.Superiores
(@tripulante_id AS INT) RETURNS TABLE
AS
RETURN
-- Iniciando CTE
WITH Superiores AS (
-- Membro Âncora
SELECT	tripulante_id, superior_id, 0 AS nivel
FROM dbo.Tripulantes
WHERE tripulante_id = @tripulante_id

UNION ALL
-- Membro Recursivo
SELECT Pai.tripulante_id, Pai.superior_id, Filho.nivel + 1
FROM Superiores AS Filho
JOIN dbo.Tripulantes AS Pai
ON Filho.superior_id = Pai.tripulante_id
)
SELECT tripulante_id, superior_id, nivel
FROM Superiores;

Neste código é necessário manter o código do tripulante superior (superior_id) para que cada recursão da CTE consiga subir um nível, a parte este detalhe, o código é similar ao código para subordinados.

Verificando a cadeia de comando para o Navegador Aprendiz Chekov, temos:

recursivo_funcao_superior_resultado

CAMINHO HIERÁRQUICO – MÉTODO RECURSIVO

Como mostrei no capítulo anterior, o caminho hierárquico registra os nós necessários para encontrar um determinado nó partindo da raiz. Geralmente é utilizado um separador da sua escolha para delimitar cada nó, então para o nó raiz o caminho seria “\” + raiz + “\” e para os próximos nós será “caminho do nó pai” + nó + “\”. Reutilizando a função de subordinados para trazer também o caminho hierárquico, temos:

USE tempdb;
GO
IF OBJECT_ID('dbo.SubordinadosCH') IS NOT NULL
DROP FUNCTION dbo.SubordinadosCH;
GO
CREATE FUNCTION dbo.SubordinadosCH
(@raiz AS INT) RETURNS TABLE
AS
RETURN
-- Iniciando CTE
WITH SubordinadosCH AS (
-- Membro Âncora
SELECT	tripulante_id, 0 AS nivel,
      CAST('\' + CAST(tripulante_id AS VARCHAR) + '\' AS VARCHAR(MAX)) AS caminho
FROM dbo.Tripulantes 
WHERE tripulante_id = @raiz

UNION ALL
-- Membro Recursivo
SELECT	Filho.tripulante_id, Pai.nivel + 1 AS nivel,
   CAST(Pai.caminho + CAST(Filho.tripulante_id AS VARCHAR) + '\' AS VARCHAR(MAX))
FROM SubordinadosCH AS Pai
JOIN dbo.Tripulantes AS Filho
ON Pai.tripulante_id = Filho.superior_id
)
SELECT tripulante_id,nivel,caminho
FROM SubordinadosCH;

Algo que vale lembrar nesta função é a utilização do CAST para garantir que o atributo “caminho” de todas as recursões tenha o mesmo tamanho e tipo de dados. Caso isso não aconteça, não é possível realizar o UNION ALL e o código quebra.

Olhando o caminho hierárquico para todos os subordinados do Capitão Kirk, incluindo ele, temos:

recursivo_funcao_caminho_resultado

Agora que já existe o caminho hierárquico e os níveis, utilizarei esta função para a última solicitação.

APRESENTAÇÃO GRÁFICA – MÉTODO RECURSIVO

Para a apresentação gráfica, utilizarei a mesma técnica apresentada no capítulo anterior. Usarei o REPLICATE para identificar os níveis na hierarquia e a ordenação pelo atributo “caminho”, criado no passo anterior. Desta forma, temos:

recursivo_funcao_caminho_apresentacao_resultado

Por questões gráficas, retornei os resultados como texto. O essencial desta técnica é replicar uma string pelo número de níveis de cada nó. Desta forma, a estrutura fica montada e ordenada como desejado.

Aqui encerro o post de hoje! Na próxima parte, irei materializar o atributo “caminho” para mostrar formas diretas e eficientes de consultar uma hierarquia. Espero que tenha gostado! Até mais!

REFERÊNCIAS

[1] Recursive Queries Using Common Table Expressions

Trabalhando com dados hierárquicos – Parte II

No capítulo anterior desta série, Trabalhando com dados hierárquicos – Parte I, descrevi o que são dados hierárquicos e defini algumas terminologias que utilizarei nesta e nas próximas partes. Apresentei o script para a criação das tabelas e ilustrei com maestria a estrutura hierárquica que servirá como base. Caso não tenha visto o post anterior, volte para a primeira parte antes de continuar.

Neste capítulo, falarei sobre o método mais intuitivo – para mim – de lidar com dados hierárquicos, o método iterativo.

MÉDOTO ITERATIVO

No método iterativo, utilizarei loops para varrer a estrutura hierárquica. Existem soluções que varrerão a árvore de nó em nó, mas essas geralmente são as escolhas mais custosas e lentas. Neste artigo utilizarei soluções iterativas que varrerão a árvore de nível em nível. Desta forma, serão feitas menos iterações nos laços de repetição.

jean_luc_cursor

Isso mesmo, Jean Luc. Esse comportamento iterativo é típico de cursores, mas veremos que esta solução pode ser uma saída flexível para nosso problema, e para situações onde nossa massa de dados é muito grande pode ser nossa única solução – veremos como resolver isso depois com a materialização da hierarquia.

Uma das maiores vantagens dos métodos iterativo e recursivo é que não precisamos alterar ou acrescentar nenhuma estrutura. Dependemos apenas dos dados para criar nossas soluções. Outra vantagem importante é não ter limite de níveis em nossa hierarquia. Podemos varrer quantos níveis quisermos.

SUBORDINADOS – MÉTODO ITERATIVO

Traduzindo a solicitação dos subordinados de um determinado nó através do método iterativo temos o seguinte algoritmo para montarmos nosso script:

  1. Criar uma tabela temporária para armazenar nossa hierarquia;
  2. Inserir nossa raiz no nível inicial (i.e. 0);
  3. Enquanto tivermos resultados, vamos inserir no próximo nível (i.e. 1) de nossa tabela temporária todos os nós cujos pais estejam entre os nós do nível anterior (i.e. 0);

Elaborando nosso script para trazer as patentes subordinadas à patente de Comandante, teríamos algo como o script abaixo.

USE tempdb;
GO
IF OBJECT_ID('tempdb..#Subordinados') IS NOT NULL
DROP TABLE #Subordinados;
GO
-- Criar a tabela temporária
CREATE TABLE #Subordinados (
	patente_id INT PRIMARY KEY,
	nivel INT NOT NULL,
	UNIQUE CLUSTERED(nivel,patente_id)
);
-- Declarar e iniciar a variável
DECLARE @nivel INT;
SET @nivel = 0;
-- Inserir a raiz
INSERT INTO #Subordinados(patente_id,nivel)
SELECT patente_id, @nivel
FROM dbo.Patentes
WHERE patente_id = 2 -- Comandante
-- Iniciar loop
WHILE(@@ROWCOUNT > 0)
BEGIN
	-- Avançar um nivel
	SET @nivel = @nivel + 1;
	-- Inserir subordinados (filhos)
	INSERT INTO #Subordinados(patente_id,nivel)
	SELECT Filho.patente_id, @nivel
	FROM #Subordinados AS Pai
	JOIN dbo.Patentes AS Filho
	ON Pai.patente_id = Filho.patente_superior_id
	WHERE Pai.nivel = @nivel - 1; -- Apenas nivel anterior
END
-- Retornar a hierarquia
SELECT patente_id, nivel
FROM #Subordinados;

Na criação da tabela temporária, acrescentei o índice único que, além de ajudar nos filtros da consulta, favorece a busca por nível na hierarquia. Esse tipo de índice também é chamado de breadth-first index. Depois, inicio a variável que controlará o nível da árvore e insiro o nó raiz que neste caso é a patente de Comandante. Então entro no loop, incremento o nível e insiro os filhos cujos pais pertençam ao nível anterior através de um JOIN da tabela temporária com a tabela base. Finalmente, retorno a hierarquia de subordinados à patente de Comandante. Para retornar não só a hierarquia, mas também a descrição de cada patente, basta realizar um JOIN da tabela resultante com a tabela base.

consulta_iterativa

Provavelmente, você irá reutilizar esse código ou terá que disponibilizar essa solução para outros. Para isso pode-se encapsular a lógica acima numa stored procedure ou numa função com valor de tabela (table-valued function). Como a estrutura que estou utilizando é pequena, optarei pela função, mas sugiro que valide o desempenho antes de descartar uma sp. Então, adaptando a solução anterior para retornar os tripulantes subordinados ao Tenente-Comandante Kelso resultaria numa função como a mostrada abaixo.

USE tempdb;
GO
IF OBJECT_ID('dbo.Subordinados') IS NOT NULL
DROP FUNCTION dbo.Subordinados;
GO
CREATE FUNCTION dbo.Subordinados
(@raiz AS INT) RETURNS @Subordinados TABLE
(
	tripulante_id INT PRIMARY KEY,
	nivel INT NOT NULL
	UNIQUE CLUSTERED(nivel, tripulante_id)
)
AS
BEGIN
-- Declarar e iniciar a variável
DECLARE @nivel INT;
SET @nivel = 0;
-- Inserir a raiz
INSERT INTO @Subordinados(tripulante_id, nivel)
SELECT tripulante_id, @nivel
FROM dbo.Tripulantes 
WHERE tripulante_id = @raiz;
-- Iniciar loop
WHILE(@@ROWCOUNT > 0)
BEGIN
	-- Avançar um nivel
	SET @nivel = @nivel + 1;
	-- Inserir subordinados (filhos)
	INSERT INTO @Subordinados(tripulante_id, nivel)
	SELECT Filho.tripulante_id, @nivel
	FROM @Subordinados AS Pai
	JOIN dbo.Tripulantes AS Filho
	ON Pai.tripulante_id = Filho.superior_id
	WHERE Pai.nivel = @nivel - 1; -- Apenas nivel anterior
END
-- Retornar a hierarquia
RETURN;
END
GO

Verificando a função com um JOIN com a tabela base da vez – dbo.Tripulantes – temos o seguinte resultado.

consulta_iterativa_funcao

SUPERIORES – MÉTODO ITERATIVO

Para obter os superiores, utilizarei a mesma lógica do processo de definir os subordinados, mas ao invés de descer níveis na hierarquia, vou subir. Como existe apenas um caminho do nó folha até o nó raiz, poderei utilizar mais este recurso como verificação. Logo, a função para obter a cadeia de comando do Oficial de Comunicações Farrell pareceria com o código abaixo.

USE tempdb;
GO
IF OBJECT_ID('dbo.Superiores') IS NOT NULL
DROP FUNCTION dbo.Superiores;
GO
CREATE FUNCTION dbo.Superiores
(@tripulante_id AS INT) RETURNS @Superiores TABLE
(
	tripulante_id INT PRIMARY KEY,
	nivel INT NOT NULL
)
AS
BEGIN
-- Declarar e iniciar a variável
DECLARE @nivel INT;
SET @nivel = 0;
-- Iniciar loop
WHILE(@tripulante_id IS NOT NULL)
BEGIN
	-- Inserir nó atual
	INSERT INTO @Superiores(tripulante_id, nivel) 
VALUES (@tripulante_id,@nivel);
	-- Avançar um nivel
	SET @nivel = @nivel + 1;
	-- Recuperar próximo gerente
	SET @tripulante_id = (SELECT superior_id
				  FROM dbo.Tripulantes
				  WHERE tripulante_id = @tripulante_id);

END
-- Retornar a hierarquia
RETURN;
END
GO

Para esta função, estou utilizando o fato de que existe apenas um superior direto para cada tripulante. Desta forma a codificação se torna bem mais simples. Ressalto que não estou validando o parâmetro de entrada, o que poderia gerar resultados inesperados ou erros. No entanto, como o objetivo aqui é mostrar uma ideia, deixo o tratamento de erros com você.

Verificando os resultados para esta função, temos o seguinte resultado:

iteracao_superior_consulta

CAMINHO HIERÁRQUICO – MÉTODO ITERATIVO

Para definir o caminho hierárquico é necessário varrer a hierarquia também. O início do caminho será o nó raiz da sub-árvore escolhida delimitado pelo separador. Então o caminho para a raiz será “\” + raiz+ “\” e para os próximos níveis será “caminho do pai” + nó + “\”. Desta forma, é possível reutilizar a função Subordinados e adaptá-la para trazer também o caminho hierárquico.

USE tempdb;
GO
IF OBJECT_ID('dbo.SubordinadosCH') IS NOT NULL
DROP FUNCTION dbo.SubordinadosCH;
GO
CREATE FUNCTION dbo.SubordinadosCH
(@raiz AS INT) RETURNS @Subordinados TABLE
(
	tripulante_id INT PRIMARY KEY,
	nivel INT NOT NULL,
	caminho VARCHAR(20)
	UNIQUE CLUSTERED(nivel, tripulante_id)
)
AS
BEGIN
-- Declarar e iniciar a variável
DECLARE @nivel INT;
SET @nivel = 0;
-- Inserir a raiz
INSERT INTO @Subordinados(tripulante_id, nivel, caminho)
SELECT	tripulante_id, @nivel, 
		'.' + CAST(tripulante_id AS VARCHAR) + '.'
FROM dbo.Tripulantes WHERE tripulante_id = @raiz;
-- Iniciar loop
WHILE(@@ROWCOUNT > 0)
BEGIN
	-- Avançar um nivel
	SET @nivel = @nivel + 1;
	-- Inserir subordinados (filhos)
	INSERT INTO @Subordinados(tripulante_id, nivel, caminho)
	SELECT	Filho.tripulante_id, @nivel, 
			Pai.caminho + CAST(Filho.tripulante_id AS VARCHAR) + '.'
	FROM @Subordinados AS Pai
	JOIN dbo.Tripulantes AS Filho
	ON Pai.tripulante_id = Filho.superior_id
	WHERE Pai.nivel = @nivel - 1; -- Apenas nivel anterior
END
-- Retornar a hierarquia
RETURN;
END
GO

Para definir o caminho, utilizei o atributo “tripulante_id”, mas você pode substituí-lo facilmente pelo nome do tripulante para seguir o caminho mais claramente. No entanto, fique alerta para o tamanho do atributo “caminho”, pois este pode crescer muito. Retornando o resultado da função para o Capitão Kirk, temos:

iterativo_caminho_consulta

Através da ordenação dos resultados pelo atributo “caminho”, fica visualmente fácil de enxergarmos a hierarquia. Utilizando este atributo novo, partiremos para a última solicitação.

APRESENTAÇÃO GRÁFICA – MÉTODO ITERATIVO

A apresentação gráfica de uma hierarquia seguirá a ordenação do novo atributo “caminho”, para organizar os resultados utiliza-se uma string – escolhida pelo usuário – repetida pelo número do nível do nó. Desta forma, temos o seguinte resultado:

iterativo_apresentacao_consulta

Como o caminho de cada nó filho é naturalmente maior que o do nó pai – por conter o nó pai –, a ordenação pelo “caminho” retorna perfeitamente a estrutura hierárquica. Utilizei o retorno dos resultados no formato texto para melhor ilustrar nossa apresentação.

Assim, encerro o método iterativo. Espero que tenha gostado! Na próxima parte irei demonstrar estas soluções utilizando CTEs recursivas. Até lá!

Trabalhando com dados hierárquicos – Parte I

enterprise
Dados hierárquicos: a fronteira final. Essas são as viagens de um DBA na nave MSSQL Enterprise. Sua missão: explorar novas hierarquias, procurar novas formas de consultar e materializar dados hierárquicos, ir onde um número razoável de DBAs já foi.

Hoje trouxe para vocês um assunto que normalmente causa arrepios quando se trata de consultas: dados hierárquicos. Na primeira parte, vou começar descrevendo o que são dados hierárquicos num banco de dados e como identificá-los. Nas partes seguintes, veremos as formas mais comuns de consultas a hierarquias – através de iteração e recursividade – e então iremos materializar uma hierarquia para melhorar o desempenho de nossas consultas. Finalmente, iremos aprender a converter dados hierárquicos numa estrutura materializada com o hierarchyid e conhecer alguns métodos deste “novo” tipo de dados.

O QUE SÃO DADOS HIERÁRQUICOS?

Dados hierárquicos são, em sua maioria, registros de uma mesma entidade que possuem uma ligação que estabelece uma relação de parentesco ou subordinação. Uma das representações mais comuns de uma hierarquia é a relação gerente/funcionário. No entanto, existem diversas estruturas hierárquicas fáceis de identificar, como as relações entre pai/filho ou país/estado/cidade, etc. Essas estruturas são representadas por árvores invertidas onde a raiz fica no topo. Cada nó da árvore – representados com círculos – ou é um nó interno ou um nó folha. Um nó interno é chamado de pai de seus nós filhos e pode ter um ou mais filhos. Filhos de um mesmo nó interno são chamados de irmãos como mostra a figura abaixo.

Adaptada do livro "Inside Microsoft SQL Server 2008: T-SQL Querying"

Imagem adaptada do livro “Inside Microsoft SQL Server 2008: T-SQL Querying”

Para entendermos melhor essa estrutura hierárquica, vamos criar duas tabelas e inserir alguns dados.

SET NOCOUNT ON;
USE [tempdb];
GO
-- Vamos garantir que as tabelas não existam
IF(OBJECT_ID('dbo.Patentes') IS NOT NULL) DROP TABLE dbo.Patentes;
GO
IF(OBJECT_ID('dbo.Tripulantes') IS NOT NULL) DROP TABLE dbo.Tripulantes;
GO
-- Criando a tabela de Patentes
CREATE TABLE dbo.Patentes (
	patente_id INT PRIMARY KEY,
	patente_superior_id INT NULL REFERENCES dbo.Patentes,
	patente_descricao VARCHAR(20) NOT NULL,
	CHECK(patente_id <> patente_superior_id)
);
-- Criando a tabela de Tripulantes
CREATE TABLE dbo.Tripulantes (
	tripulante_id INT PRIMARY KEY,
	superior_id	INT NULL REFERENCES dbo.Tripulantes,
	tripulante_nome VARCHAR(20) NOT NULL,
	patente_id INT NOT NULL,
	cargo VARCHAR(20) NOT NULL,
	CHECK(tripulante_id <> superior_id)
);
-- Inserindo Patentes
INSERT INTO dbo.Patentes(patente_id,patente_superior_id,patente_descricao) VALUES
(1,   NULL,  'Capitão'),
(2,   1,     'Comandante'),
(3,   2,     'Tenente-Comandante'),
(4,   3,     'Tenente'),
(5,   4,     'Alferes');
-- Inserindo a tripulação
INSERT INTO dbo.Tripulantes (tripulante_id,superior_id,tripulante_nome,patente_id,cargo) VALUES
(1,     NULL,   'Kirk',     1,	'Capitão Encarregado'),
(2,     1,      'Spock',    2,	'Primeiro Oficial'),
(3,     1,      'Scott',    2,	'Chefe de Engenharia'),
(4,     1,      'Uhura',    3,	'Chefe de Comunicações'),
(5,     1,      'McCoy',    3,	'Médico'),
(6,     1,      'Kelso',    3,	'Chefe de Navegação'),
(7,     1,      'Sulu',     3,	'Chefe dos Timoneiros'),
(8,     4,      'Farrell',  4,	'Oficial de Comunicações'),
(9,     4,      'Palmer',   4,	'Oficial de Comunicações'),
(10,    6,      'Stiles',   4,	'Navegador'),
(11,    6,      'Osborne',  4,	'Navegador'),
(12,    7,      'Leslie',   4,	'Timoneiro'),
(13,    7,      'Hansen',   4,	'Timoneiro'),
(14,    7,      'DePaul',   4,	'Timoneiro'),
(15,    11,     'Chekov',   5,	'Navegador Aprendiz'),
(16,    11,     'Haines',   5,	'Navegador Aprendiz'),
(17,    14,     'Dawson',   5,	'Timoneiro Aprendiz');

Nesse script, criamos uma tabela de patentes que possui uma hierarquia militar e uma tabela de tripulantes que possui outra hierarquia – a de comando. A hierarquia militar define níveis entre as patentes, onde o Capitão é o superior do Comandante, o Comandante é o superior do Tenente-Comandante e assim por diante. No entanto, na estrutura de comando, é definida uma relação direta de tripulante e superior. Em outras palavras, apesar da patente de Alferes ser subordinada à patente de Tenente, o Navegador Aprendiz Chekov é subordinado diretamente apenas ao Navegador Osborne e não ao Timoneiro Hansen mesmo que este também seja tenente. Caso desenhássemos nossa hierarquia de comando da Enterprise, teríamos algo como a figura abaixo.

E pensar que desisti de ser desenhista... tsc tsc...

E pensar que desisti de ser desenhista…

Agora que temos nossa hierarquia bem clara, vamos às solicitações mais comuns desse tipo de estrutura.

SUBORDINADOS

Provavelmente a solicitação mais comum de uma estrutura hierárquica é retornar os subordinados – ou filhos – de um determinado indivíduo – ou nó. Nesta pesquisa não buscamos apenas os subordinados diretos, mas todos direta e indiretamente subordinados ao nó. Em outras palavras, queremos toda a sub-árvore originada de um determinado nó.

Ex.:
Para retornarmos os subordinados do Tenente-Comandante Sulu, precisamos descer um nível hierárquico e analisar quais tenentes possuem o Sulu como seu superior. Neste caso, temos os tenentes Leslie, Hansen e DePaul. Então, desceremos mais um nível hierárquico para analisar se algum dos tenentes retornados na iteração anterior possui algum subordinado. Para este caso, observamos que o Tenente DePaul possui o Alferes Dawson como seu subordinado. Desta forma, repetimos este processo até que não retornem mais registros.

SUPERIORES

Outra solicitação comum numa hierarquia é retornar todos os superiores – ou pais – de um determinado nó. Da mesma forma que vimos para os subordinados, não buscamos apenas os superiores diretos, mas toda estrutura superior. Para nossa hierarquia, isto significa um único caminho até o topo (raiz) da árvore.

Ex.:
Para obtermos os superiores do Tenente Palmer, precisamos subir um nível hierárquico e retornar apenas o nó que possua o Palmer como subordinado (filho). Desta forma, repetiremos o mesmo processo para a Tenente-Comandante Uhura até chegar ao Capitão Kirk.

CAMINHO HIERÁRQUICO
Para esta solicitação, não queremos apenas os superiores ou subordinados, mas o caminho trilhado na hierarquia do nó de partida até o nó de destino. Para isso utilizaremos algum separador como “.” ou “\” para delimitar os nós.

Ex.:
O caminho hierárquico do Tenente-Comandante Sulu até o Alferes Dawson seria definido por “.Sulu.DePaul.Dawson.” ou “\Sulu\DePaul\Dawson\”.

APRESENTAÇÃO GRÁFICA

Nesta solicitação, o usuário deseja um resultado gráfico que demonstre a hierarquia desejada. Para representar graficamente, utilizaremos o nível e o caminho hierárquicos.

Ex.:
Para retornarmos graficamente os subordinados do Tenente-Comandante Sulu, podemos ter algo como a figura abaixo.

RepresentacaoGrafica_Hierarquia

Nos próximos capítulos dessa série, utilizaremos alguns métodos diferentes para atender às solicitações apresentadas. Ao final do artigo, você poderá decidir qual lhe servirá melhor. Até lá!