Pular para o conteúdo principal

O que devemos conhecer sobre SQL/Oracle Parte 16: Recuperação hierárquica


Interpretando o conceito de consulta hierárquica

Consultas hierárquicas vão além do conceito relacional pai/filho, onde temos uma tabela que é referenciada por outra, onde para cada registro de uma podemos ter vários em outra. 
A estrutura de dados hierárquicos é diferente, ao invés dos dados estarem em tabelas diferentes eles ficam na mesma tabela (ou view), onde uma coluna do registro faz referencia a outra coluna de um outro registro, dessa forma teoricamente existe a capacidade de se ter infinitas gerações.



Para embasar nossos próximos exemplos construiremos uma tabela nesses moldes, onde teremos informações para relatórios de funcionários:

CREATE TABLE RELATORIO_CARGOS(CODIGO NUMBER(10) PRIMARY KEY, CARGO VARCHAR2(50), RESPONDE_PARA NUMBER(10));
ALTER TABLE RELATORIO_CARGOS ADD CONSTRAINT RELATORIO_CARGOS_FK FOREIGN KEY(RESPONDE_PARA) REFERENCES RELATORIO_CARGOS(CODIGO);

Resumindo:
  • Uma consulta hierárquica estende o relacionamento pai-filho em uma estrutura multigerencial, com múltiplos níveis de relacionamento que podem ser adicionados a uma tabela, permitindo a criação de uma estrutura relacional sem fim;
  • Consultas hierárquicas são baseadas em self-join;
  • Todos os registros de uma consulta hierárquica representam um nó (node);
  • O ponto inicial de uma consulta hierárquica é considerado o nó raiz (root);
  • Qualquer nó com dois ou mais filhos é considerado um garfo (fork);
  • Qualquer nó que termina sem filhos é considerado folha (leaf);


Criando e formatando dados hierárquicos

Utilizando a estrutura definida anteriormente podemos partir para a consulta, onde veremos duas novas cláusulas, START WITH, que identifica o nosso nó raiz, aqui é uma boa prática identificar uma coluna que seja chave primaria, mas isso não é obrigatório, qualquer expressão que identifique um registro de forma única pode ser usado, e também usaremos a cláusula CONNECT BY e PRIOR, responsável por realizar o SELF-JOIN, é onde indicamos as colunas que relacionam os registros, e mais importante, qual será o sentido dessa busca, sendo que o PRIOR deve ser indicado antes de uma das colunas utilizadas no SELF-JOIN:

SELECT LEVEL, CODIGO, CARGO, RESPONDE_PARA FROM RELATORIO_CARGOS START WITH CODIGO = 1 CONNECT BY RESPONDE_PARA = PRIOR CODIGO;

Na consulta acima indicamos uma pseudocoluna chamada LEVEL, ela irá mostrar o nível de hierarquia de cada registro, sendo que ele é gerado a cada consulta, em nosso caso o nível um será o registro com código um, se na cláusula START WITH tivéssemos indicado outro código esse passaria a ser o nível um.
Um uso comum é a construção de estruturas em arvore, onde podemos formatar uma das colunas com espaços, por exemplo, para facilitar a visualização de nossa estrutura:

SELECT LEVEL, CODIGO, (LPAD('   ', LEVEL*3) || CARGO) CARGO FROM RELATORIO_CARGOS START WITH CODIGO = 1 CONNECT BY RESPONDE_PARA = PRIOR CODIGO;

Como mencionado antes à direção do resultado da consulta é definida pela cláusula PRIOR, em nosso exemplo o resultado inicia na raiz de nossa estrutura e vai descendo, mas se colocarmos o PRIOR antes da coluna RESPONDE_PARA o sentido será o oposto, alterando apenas isso termos apenas um registro como resultado, considerando que o código um é o raiz, logo para um sentido maior usaremos um código maior:

SELECT LEVEL, CODIGO, (LPAD('   ', LEVEL*3) || CARGO) CARGO FROM RELATORIO_CARGOS START WITH CODIGO = 15 CONNECT BY PRIOR RESPONDE_PARA =  CODIGO;

Ao indicarmos o código quinze nosso resultado passa a contemplar apenas os registros que estão diretamente ligados a ele, sendo que agora ele passa a ser o nível um.
Para a ordenação deve-se tomar cuidado, o resultado é sempre formado nível a nível, sendo que os registros são posicionados logo abaixo de quem estão diretamente relacionados, uma ordenação faria com que esses registros se misturassem, perdendo assim o sentido da apresentação. Por isso para ordenar utilizamos uma nova cláusula ORDER SIBLINGS BY, que realiza a ordenação sem misturar os níveis:

SELECT LEVEL, CODIGO, (LPAD('   ', LEVEL*3) || CARGO) CARGOF FROM RELATORIO_CARGOS START WITH CODIGO = 1 CONNECT BY RESPONDE_PARA = PRIOR CODIGO ORDER SIBLINGS BY CARGO;

É possível ainda listar toda a hierarquia em cada registro, utilizando a função SYS_CONNECT_BY_PATH:

SELECT LEVEL, CODIGO, SYS_CONNECT_BY_PATH(CARGO, '/') CARGO FROM RELATORIO_CARGOS START WITH CODIGO = 1 CONNECT BY RESPONDE_PARA = PRIOR CODIGO;

Outra função interessante é o CONNECT_BY_ROOT, capaz de mostrar qualquer informação do nó raiz da consulta:

SELECT LEVEL, CODIGO, CARGO, CONNECT_BY_ROOT CARGO AS CARGO_ROOT FROM RELATORIO_CARGOS START WITH CODIGO = 4 CONNECT BY RESPONDE_PARA = PRIOR CODIGO;

Resumindo:
  • As cláusulas START WITH e CONNECT BY são usadas para formar uma consulta hierárquica;
  • A cláusula START WITH identifica o nó raiz (root);
  • A cláusula CONNECT BY define o relacionamento self-join;
  • Deve existir ao menos uma cláusula PRIOR junto ao CONNECT BY;
  • O PRIOR determina a direção da consulta hierárquica;
  • A pseudocoluna LEVEL identifica o nível de geração do nó;
  • A cláusula ORDER SIBLINGS BY ordena registros pelo nível hierárquico sem comprometer o resultado;
  • A função SYS_CONNECT_BY_PATH mostra o caminho completo dos nós;
  • O operador SYS_CONNECT_BY_ROOT faz referencia ao nó raiz (root) de qualquer nó;
  • A ordem das cláusulas, se usadas, devem ser SELECT, FROM, WHERE, START WITH, CONNECT BY, e ORDER BY;


Excluindo ramos da estrutura em arvore

Até agora em nossos exemplos não removemos nenhum registro do resultado, mas isso é possível, poderíamos utilizar a cláusula WHERE para limitar algum cargo, por exemplo, mas isso não necessariamente removeria um ramo de nossa estrutura, não que isso não possa ser feito, mas existe uma forma melhor para tal, é incluir essa remoção na cláusula CONNECT BY:

SELECT LEVEL, CODIGO, (LPAD('   ', LEVEL*3) || CARGO) CARGOF FROM RELATORIO_CARGOS START WITH CODIGO = 1 CONNECT BY RESPONDE_PARA = PRIOR CODIGO AND CODIGO != 4;

No exemplo acima o nó de código quatro e todos os seus descendentes foram removidos do resultado.

Resumindo:
  • A cláusula CONNECT BY pode ser usada para excluir ramos inteiros da arvore;
  • A cláusula WHERE pode excluir registros individualmente, mas não irá excluir os ramos de forma automática;


Comentários

Mais visitadas

Lista de políticos com ficha suja

ATUALIZAÇÃO (08/03/2012 ano de eleição) Representantes de duas pessoas da lista (Eliseu Padilha e Alex Canziani) entraram em contato e pediram que esses fossem removidos, alegando que não houve condenação. É justo essa requisição, porém vale lembrar que escândalos nacionalmente reconhecidos de corrupção não deram em nada, por isso o que realmente conta nesse ano de eleição é uma pesquisa minuciosa sobre os candidatos escolhidos, eu particularmente, por não estar nem um pouco satisfeito, não irei votar em ninguém que já tenha sido eleito. Estou voltando a postar por um motivo nobre, meu tempo continua apertado mas esse post é rápido, na verdade nem meu ele é (visitem o espaço de nosso amigo Lord ), estou apenas repassando essa valiosa informação. Teremos eleição esse ano, e é importante não repetirmos erros passados, vamos ficar atentos em relação a esses nomes e exclui-los de vez do cenário politico nacional. Façamos nossa parte, publicando em nossos blog...

Centralizar Texto em Edit

Como todos sabemos o Edit mantém todo texto digitado a esquerda, o que não fica bem quando o usamos para a entrada de números, pois bem, o exemplo abaixo apresenta uma alternativa para centralizar um determinado valor dentro de um Edit: procedure EditChange(Sender: TObject); var vl_label : TLabel; //variável do tipo Label begin vl_label := TLabel.Create(self); //criamos um label WITH vl_label DO BEGIN Font.Name := TEdit(sender).Font.Name; //pegamos a fonte usada no edit Caption := TEdit(sender).Text; //pegamos o conteúdo do edit SendMessage(TEdit(sender).Handle, EM_SETMARGINS, EC_LEFTMARGIN, (TEdit(sender).Width-vl_label.Width) div 2); //centraliza no label e retorna para o edit END ; vl_label.Free; end ;

Como pegar valor de coluna invisível do GridView

Em determinado momento nos deparamos com a seguinte situação, nossa tabela do banco de dados possui como campo chave, um ID por exemplo, incrementado por uma rotina da aplicação, do banco ou algum outro mecanismo de incremento (sequences, increment, etc), logo não há porque mostrar essa coluna para o usuário porque o mesmo não sabe de sua existência, e seu valor não tem qualquer relevância para ele, sendo assim o mais lógico a se fazer é não mostra-lá na aplicação. Seu valor é necessário já que por se tratar de uma chave primária precisaremos dela para atualizar, inserir ou excluir os dados, então o que fazer? A resposta seria simples, deixemos ele invisível, buscaremos seu valor e o usuário não perceberá, porém a partir da versão 2005 do visual Studio as colunas setadas com "visible = false" não são renderizadas, com isso ao se tentar acessar seu valor o que recebemos é nada. Para contornarmos essa situação podemos codificar o seguinte: protected void GridView1_R...