MySQL: requête hiérarchique arborescente

20

SOUS-ARBRE DANS UN ARBRE dans MySQL

Dans mon MYSQL Database COMPANY, j'ai une Table: Employeeassociation récursive, un employé peut être patron d'un autre employé. A self relationship of kind (SuperVisor (1)- SuperVisee (∞) ).

Requête pour créer une table:

CREATE TABLE IF NOT EXISTS `Employee` (
  `SSN` varchar(64) NOT NULL,
  `Name` varchar(64) DEFAULT NULL,
  `Designation` varchar(128) NOT NULL,
  `MSSN` varchar(64) NOT NULL, 
  PRIMARY KEY (`SSN`),
  CONSTRAINT `FK_Manager_Employee`  
              FOREIGN KEY (`MSSN`) REFERENCES Employee(SSN)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

J'ai inséré un ensemble de tuples (Requête):

INSERT INTO Employee VALUES 
 ("1", "A", "OWNER",  "1"),  

 ("2", "B", "BOSS",   "1"), # Employees under OWNER 
 ("3", "F", "BOSS",   "1"),

 ("4", "C", "BOSS",   "2"), # Employees under B
 ("5", "H", "BOSS",   "2"), 
 ("6", "L", "WORKER", "2"), 
 ("7", "I", "BOSS",   "2"), 
 # Remaining Leaf nodes   
 ("8", "K", "WORKER", "3"), # Employee under F     

 ("9", "J", "WORKER", "7"), # Employee under I     

 ("10","G", "WORKER", "5"), # Employee under H

 ("11","D", "WORKER", "4"), # Employee under C
 ("12","E", "WORKER", "4")  

Les lignes insérées ont la relation arborescente-hiérarchique suivante :

         A     <---ROOT-OWNER
        /|\             
       / A \        
      B     F 
    //| \    \          
   // |  \    K     
  / | |   \                     
 I  L H    C        
/     |   / \ 
J     G  D   E

J'ai écrit une requête pour trouver une relation:

SELECT  SUPERVISOR.name AS SuperVisor, 
        GROUP_CONCAT(SUPERVISEE.name  ORDER BY SUPERVISEE.name ) AS SuperVisee, 
        COUNT(*)  
FROM Employee AS SUPERVISOR 
  INNER JOIN Employee SUPERVISEE ON  SUPERVISOR.SSN = SUPERVISEE.MSSN 
GROUP BY SuperVisor;

Et la sortie est:

+------------+------------+----------+
| SuperVisor | SuperVisee | COUNT(*) |
+------------+------------+----------+
| A          | A,B,F      |        3 |
| B          | C,H,I,L    |        4 |
| C          | D,E        |        2 |
| F          | K          |        1 |
| H          | G          |        1 |
| I          | J          |        1 |
+------------+------------+----------+
6 rows in set (0.00 sec)

[ QUESTION ]
Au lieu d'un arbre hiérarchique complet, j'ai besoin SUB-TREEd'un point (sélectif), par exemple:
si l'argument d'entrée est Balors la sortie doit être comme ci-dessous ...

+------------+------------+----------+
| SuperVisor | SuperVisee | COUNT(*) |
+------------+------------+----------+
| B          | C,H,I,L    |        4 |
| C          | D,E        |        2 |
| H          | G          |        1 |
| I          | J          |        1 |
+------------+------------+----------+   

Veuillez m'aider à ce sujet. Sinon, une procédure stockée peut être utile.
J'ai essayé, mais tous les efforts ont été inutiles!

Grijesh Chauhan
la source
J'ai simplement fourni un cadre de test que la communauté pourrait utiliser pour explorer cette question plus facilement.
mellamokb
@bluefeet Oui, une fois que j'aurai une réponse, je supprimerai l'un des deux.
Grijesh Chauhan
1
@GrijeshChauhan permettez-moi de vous demander ceci: lequel vaut mieux faire vos propres ondes visibles? Pour jeter des cailloux dans l'océan, ou pour jeter des rochers dans un petit étang? Aller directement aux experts va certainement vous donner la meilleure réponse, et ce type de question est si important (sujets avancés de base de données) que nous lui avons donné son propre site sur le réseau. Mais je ne vous empêcherai pas de lui demander où vous voulez, c'est votre prérogative. Ma prérogative est de voter pour le déplacer vers un autre site si je pense que c'est là qu'il appartient. : D Nous utilisons tous les deux le réseau comme bon nous semble dans ce cas: D
jcolebrand
1
@jcolebrand: En fait, c'était uniquement de ma faute. J'utilise pour poster une question sur plusieurs côtés pour obtenir une réponse meilleure, rapide et multiple. It my experience J'ai toujours eu une meilleure réponse de la part des experts . Et je pense qu'il était préférable de déplacer la question aux administrateurs de base de données. Dans tous les cas, je suis très reconnaissant envers stackoverflow et les personnes qui sont actives ici. J'ai vraiment trouvé une solution à de nombreux problèmes qui étaient très difficiles à trouver moi-même ou tout autre site Web.
Grijesh Chauhan

Réponses:

5

J'ai déjà abordé quelque chose de cette nature en utilisant des procédures stockées: trouver le plus haut niveau d'un champ hiérarchique: avec vs sans CTE (24 octobre 2011)

Si vous regardez dans mon article, vous pouvez utiliser les fonctions GetAncestry et GetFamilyTree comme modèle pour parcourir l'arbre à partir d'un point donné.

MISE À JOUR 2012-12-11 12:11 EDT

J'ai regardé mon code depuis mon post . J'ai rédigé la fonction stockée pour vous:

DELIMITER $$

DROP FUNCTION IF EXISTS `cte_test`.`GetFamilyTree` $$
CREATE FUNCTION `cte_test`.`GetFamilyTree`(GivenName varchar(64))
RETURNS varchar(1024) CHARSET latin1
DETERMINISTIC
BEGIN

    DECLARE rv,q,queue,queue_children,queue_names VARCHAR(1024);
    DECLARE queue_length,pos INT;
    DECLARE GivenSSN,front_ssn VARCHAR(64);

    SET rv = '';

    SELECT SSN INTO GivenSSN
    FROM Employee
    WHERE name = GivenName
    AND Designation <> 'OWNER';
    IF ISNULL(GivenSSN) THEN
        RETURN ev;
    END IF;

    SET queue = GivenSSN;
    SET queue_length = 1;

    WHILE queue_length > 0 DO
        IF queue_length = 1 THEN
            SET front_ssn = queue;
            SET queue = '';
        ELSE
            SET pos = LOCATE(',',queue);
            SET front_ssn = LEFT(queue,pos - 1);
            SET q = SUBSTR(queue,pos + 1);
            SET queue = q;
        END IF;
        SET queue_length = queue_length - 1;
        SELECT IFNULL(qc,'') INTO queue_children
        FROM
        (
            SELECT GROUP_CONCAT(SSN) qc FROM Employee
            WHERE MSSN = front_ssn AND Designation <> 'OWNER'
        ) A;
        SELECT IFNULL(qc,'') INTO queue_names
        FROM
        (
            SELECT GROUP_CONCAT(name) qc FROM Employee
            WHERE MSSN = front_ssn AND Designation <> 'OWNER'
        ) A;
        IF LENGTH(queue_children) = 0 THEN
            IF LENGTH(queue) = 0 THEN
                SET queue_length = 0;
            END IF;
        ELSE
            IF LENGTH(rv) = 0 THEN
                SET rv = queue_names;
            ELSE
                SET rv = CONCAT(rv,',',queue_names);
            END IF;
            IF LENGTH(queue) = 0 THEN
                SET queue = queue_children;
            ELSE
                SET queue = CONCAT(queue,',',queue_children);
            END IF;
            SET queue_length = LENGTH(queue) - LENGTH(REPLACE(queue,',','')) + 1;
        END IF;
    END WHILE;

    RETURN rv;

END $$

Cela fonctionne réellement. Voici un exemple:

mysql> SELECT name,GetFamilyTree(name) FamilyTree
    -> FROM Employee WHERE Designation <> 'OWNER';
+------+-----------------------+
| name | FamilyTree            |
+------+-----------------------+
| A    | B,F,C,H,L,I,K,D,E,G,J |
| G    |                       |
| D    |                       |
| E    |                       |
| B    | C,H,L,I,D,E,G,J       |
| F    | K                     |
| C    | D,E                   |
| H    | G                     |
| L    |                       |
| I    | J                     |
| K    |                       |
| J    |                       |
+------+-----------------------+
12 rows in set (0.36 sec)

mysql>

Il n'y a qu'une seule prise. J'ai ajouté une ligne supplémentaire pour le propriétaire

  • Le propriétaire a SSN 0
  • Le propriétaire est son propre patron avec MSSN 0

Voici les données

mysql> select * from Employee;
+-----+------+-------------+------+
| SSN | Name | Designation | MSSN |
+-----+------+-------------+------+
| 0   | A    | OWNER       | 0    |
| 1   | A    | BOSS        | 0    |
| 10  | G    | WORKER      | 5    |
| 11  | D    | WORKER      | 4    |
| 12  | E    | WORKER      | 4    |
| 2   | B    | BOSS        | 1    |
| 3   | F    | BOSS        | 1    |
| 4   | C    | BOSS        | 2    |
| 5   | H    | BOSS        | 2    |
| 6   | L    | WORKER      | 2    |
| 7   | I    | BOSS        | 2    |
| 8   | K    | WORKER      | 3    |
| 9   | J    | WORKER      | 7    |
+-----+------+-------------+------+
13 rows in set (0.00 sec)

mysql>
RolandoMySQLDBA
la source
compris l'idée!
Grijesh Chauhan
Comment puis-je m'adapter pour obtenir tous les descendants de Ace genre A A/B A/B/C A/B/C/D A/B/C/E A/B/H A/B/H/G A/B/I A/B/I/J A/B/L A/F A/F/K
Smith
gère-t-il également les multinœuds? car il est suspendu dans ma base de données où plusieurs nœuds d'un parent ont été trouvés
عثمان غني
3

Ce que vous utilisez est appelé modèle de liste d'adjacence . Il a beaucoup de limites. Vous aurez un problème lorsque vous souhaitez supprimer / insérer un nœud à un endroit spécifique. Il vaut mieux utiliser le modèle de jeu imbriqué .

Il y a une explication détaillée . Malheureusement l'article sur mysql.com n'existe plus.

Shiplu Mokaddim
la source
5
" il a beaucoup de limitations " - mais uniquement lors de l'utilisation de MySQL. Presque tous les SGBD prennent en charge les requêtes récursives (MySQL est l'un des rares à ne pas le faire) et cela rend le modèle très facile à gérer.
a_horse_with_no_name
@a_horse_with_no_name Jamais utilisé autre chose que MySQL. Donc je ne l'ai jamais su. Merci pour l'information.
Shiplu Mokaddim