nota: esta pergunta foi atualizada para refletir que estamos usando MySQL atualmente, tendo feito isso, gostaria de ver como seria muito mais fácil se mudássemos para um banco de dados compatível com CTE.
Eu tenho uma tabela de auto-referência com uma chave primária id
e uma chave estrangeira parent_id
.
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| parent_id | int(11) | YES | | NULL | |
| name | varchar(255) | YES | | NULL | |
| notes | text | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
Dado um name
, como posso consultar o pai de nível superior?
Dado um name
, como posso consultar todos os id
associados a um registro de name = 'foo'
?
context: Não sou dba, mas estou planejando pedir a um dba para implementar esse tipo de estrutura hierárquica e gostaria de testar algumas consultas. A motivação para isso é descrita por Kattge et al 2011 .
Aqui está um exemplo das relações entre os ids na tabela:
-- -----------------------------------------------------
-- Create a new database called 'testdb'
-- -----------------------------------------------------
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';
CREATE SCHEMA IF NOT EXISTS `testdb` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ;
USE `testdb` ;
-- -----------------------------------------------------
-- Table `testdb`.`observations`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `testdb`.`observations` (
`id` INT NOT NULL ,
`parent_id` INT NULL ,
`name` VARCHAR(45) NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
-- -----------------------------------------------------
-- Add Example Data Set
-- -----------------------------------------------------
INSERT INTO observations VALUES (1,3), (2,5), (3,NULL), (4,10),
(5,NULL), (6,1), (7,5), (8,10), (9,10), (10,3);
Você definitivamente tem que fazer um script isso via MySQL Stored Procedure Language
Aqui está uma função armazenada chamada
GetParentIDByID
para recuperar um ParentID dado um ID para pesquisarAqui está uma Função Armazenada chamada
GetAncestry
para Recuperar uma Lista de ParentIDs a partir da Primeira Geração em toda a hierarquia com um ID para começar:Aqui está algo para gerar dados de amostra:
Aqui está o que ele gera:
Aqui está o que as funções geram para cada valor:
MORAL DA HISTÓRIA: A recuperação de dados recursiva deve ser roteirizada no MySQL
ATUALIZAÇÃO 24/10/2011 17:17 EDT
Aqui está o inverso de GetAncestry. Eu chamo isso de GetFamilyTree.
Aqui está o algoritmo:
Eu acredito que nas minhas aulas de Estruturas de Dados e Algoritmos na faculdade, isso é chamado de algo como passagem de árvore de pré-ordem/prefixo.
Aqui está o código:
Aqui está o que cada linha produz
Este algoritmo funciona de forma limpa, desde que não haja caminhos cíclicos. Se existir algum caminho cíclico, você terá que adicionar uma coluna 'visitada' à tabela.
Depois de adicionar a coluna visitada, aqui está o algoritmo que bloqueia os relacionamentos cíclicos:
ATUALIZAÇÃO 24/10/2011 17:37 EDT
Criei uma nova tabela chamada observações e preenchi seus dados de amostra. Mudei os procedimentos armazenados para usar observações em vez de pctable. Aqui está sua saída:
ATUALIZAÇÃO 2011-10-24 18:22 EDT
Alterei o código para GetAncestry. Havia
WHILE ch > 1
deveria serWHILE ch > 0
Tente agora !!!
Obtendo todos os pais de um nó especificado:
Para obter o nó raiz, você pode, por exemplo
ORDER BY level
, pegar a primeira linhaObtendo todos os filhos de um nó especificado:
(observe a condição trocada para a junção na parte recursiva da instrução)
Que eu saiba, os seguintes DBMS suportam CTEs recursivos:
Editar
Com base em seus dados de exemplo, o seguinte recuperaria todas as subárvores da tabela, incluindo o caminho completo para cada nó como uma coluna adicional:
A saída seria esta:
A função GetFamilyTree na resposta de Rolando não funciona quando o id fornecido é maior que 4 inteiros, porque a função FORMAT MySQL adiciona vírgulas para separadores de milhar. Eu modifiquei a função armazenada GetFamilyTree para trabalhar com grandes ids inteiros como abaixo:
front_id definido dentro do loop if else.