2008-09-27 9 views
2

Supposons que j'ai autoréférencement la table hiérarchique construire la voie classique comme celui-ci:des données hiérarchiques de clonage

CREATE TABLE test 
(name text,id serial primary key,parent_id integer 
references test); 

insert into test (name,id,parent_id) values 
('root1',1,NULL),('root2',2,NULL),('root1sub1',3,1),('root1sub2',4,1),('root 
2sub1',5,2),('root2sub2',6,2); 

testdb=# select * from test; 

    name | id | parent_id 
-----------+----+----------- 
root1  | 1 | 
root2  | 2 | 
root1sub1 | 3 |   1 
root1sub2 | 4 |   1 
root2sub1 | 5 |   2 
root2sub2 | 6 |   2 

Ce que je besoin est maintenant une fonction (de préférence en SQL) qui prendrait l'identifiant d'un dossier de test et cloner tous les enregistrements joints (y compris le dossier donné). Les enregistrements clonés doivent bien sûr avoir de nouveaux identifiants. Le résultat souhaité souhaite ceci par exemple:

Select * from cloningfunction(2); 

    name | id | parent_id  
-----------+----+----------- 
root2  | 7 | 
root2sub1 | 8 |   7 
root2sub2 | 9 |   7 

Tous les pointeurs? Im en utilisant PostgreSQL 8.3.

Répondre

6

L'extraction récursive de ce résultat est difficile (bien que possible). Cependant, ce n'est généralement pas très efficace et il y a un beaucoup mieux pour résoudre ce problème.

Fondamentalement, vous augmentez la table avec une colonne supplémentaire qui trace l'arbre vers le haut - je l'appellerai le "Upchain". Il est juste une longue chaîne qui ressemble à quelque chose comme ceci:

name | id | parent_id | upchain 
root1 | 1 | NULL | 1: 
root2 | 2 | NULL | 2: 
root1sub1 | 3 | 1 | 1:3: 
root1sub2 | 4 | 1 | 1:4: 
root2sub1 | 5 | 2 | 2:5: 
root2sub2 | 6 | 2 | 2:6: 
root1sub1sub1 | 7 | 3 | 1:3:7: 

Il est très facile de garder ce champ mis à jour à l'aide d'un déclencheur sur la table. (Toutes mes excuses pour la terminologie mais j'ai toujours fait cela avec SQL Server). Chaque fois que vous ajoutez ou supprimez un enregistrement, ou que vous mettez à jour le champ parent_id, il vous suffit de mettre à jour le champ upchain sur cette partie de l'arbre. C'est un travail trivial parce que vous venez de prendre l'upchain de l'enregistrement parent et d'ajouter l'ID de l'enregistrement en cours. Tous les enregistrements enfant sont facilement identifiés en utilisant LIKE pour vérifier les enregistrements avec la chaîne de départ dans leur upchain. Ce que vous faites efficacement, c'est échanger un peu d'activité d'écriture supplémentaire pour un enregistrement big quand vous venez de lire les données.

Lorsque vous voulez sélectionner une branche complète dans l'arborescence, c'est trivial. Supposons que vous vouliez la branche sous le nœud 1. Le nœud 1 a un upchain '1:' ainsi vous savez que n'importe quel nœud dans la branche de l'arbre sous ce nœud doit avoir une upchain commençant '1: ...'. Alors vous faites ceci:

SELECT * 
FROM table 
WHERE upchain LIKE '1:%' 

Ceci est extrêmement rapide (index le champ bien sûr baie précédente). En prime, cela rend beaucoup d'activités extrêmement simples, comme trouver des arbres partiels, le niveau dans l'arbre, etc.

J'ai utilisé ceci dans des applications qui suivent les grandes hiérarchies de rapports d'employés mais vous pouvez l'utiliser pour bien une structure arborescente (panne de pièces, etc.)

notes (pour toute personne intéressée):

  • Je n'ai pas donné une étape par étape du code SQL, mais une fois que vous obtenez le principe , c'est assez simple à mettre en place. Je ne suis pas un bon programmeur donc je parle par expérience.
  • Si vous avez déjà des données dans la table, vous devez effectuer une mise à jour ponctuelle pour synchroniser les upchains. Encore une fois, ce n'est pas difficile car le code est très similaire au code UPDATE dans les déclencheurs.
  • Cette technique est également un bon moyen d'identifier des références circulaires qui peuvent être difficiles à repérer.
+1

Si je pouvais upvote deux fois, je le ferais ... Très bien! –

+0

Je pense que le déclencheur doit mettre à jour tous les enregistrements enfants de la hiérarchie. Par exemple. si nous mettons à jour parent_id de l'enregistrement "root2" à 1, nous devons également mettre à jour les enregistrements "root2sub1" et "root2sub2". – Panos

+0

C'est le chemin du chemin matérialisé ... très pratique –

0

Cela ressemble à un exercice de « SQL Smarties » par Joe Celko ...

Je n'ai pas ma copie à portée de main, mais je pense qu'il est un livre qui va vous aider un peu si c'est le genre de problèmes que vous devez résoudre.

1

@Maximilian: Vous avez raison, nous avons oublié vos besoins réels. Que diriez-vous d'une procédure stockée récursive? Je ne sais pas si cela est possible dans PostgreSQL, mais voici une travail SQL Server version:

CREATE PROCEDURE CloneNode 
    @to_clone_id int, @parent_id int 
AS 
    SET NOCOUNT ON 
    DECLARE @new_node_id int, @child_id int 

    INSERT INTO test (name, parent_id) 
     SELECT name, @parent_id FROM test WHERE id = @to_clone_id 
    SET @new_node_id = @@IDENTITY 

    DECLARE @children_cursor CURSOR 
    SET @children_cursor = CURSOR FOR 
     SELECT id FROM test WHERE parent_id = @to_clone_id 

    OPEN @children_cursor 
    FETCH NEXT FROM @children_cursor INTO @child_id 
    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     EXECUTE CloneNode @child_id, @new_node_id 
     FETCH NEXT FROM @children_cursor INTO @child_id 
    END 
    CLOSE @children_cursor 
    DEALLOCATE @children_cursor 

Votre exemple est accompli par EXECUTE CloneNode 2, null (le second paramètre est le nouveau nœud parent).

Questions connexes