MySQL: consulta jerárquica en árbol

20

SUB-ÁRBOL DENTRO DE UN ÁRBOL en MySQL

En mi MYSQL Database COMPANY, tengo una Table: Employeeasociación recursiva, un empleado puede ser jefe de otro empleado. A self relationship of kind (SuperVisor (1)- SuperVisee (∞) ).

Consulta para crear tabla:

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;

He insertado un conjunto de tuplas (consulta):

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")  

Las filas insertadas tienen la siguiente relación árbol-jerárquica :

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

Escribí una consulta para encontrar una relación:

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;

Y la salida es:

+------------+------------+----------+
| 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)

[ PREGUNTA ]
En lugar de completar el árbol jerárquico, necesito un SUB-TREEdesde un punto (selectivo), por ejemplo:
si el argumento de entrada es, Bentonces la salida debería ser la siguiente ...

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

Por favor ayúdame con esto. Si no se consulta, un procedimiento almacenado puede ser útil.
Lo intenté, ¡pero todos los esfuerzos fueron inútiles!

Grijesh Chauhan
fuente
Simplemente proporcioné un marco de prueba para que la comunidad lo use al explorar esta pregunta más fácilmente.
mellamokb
@bluefeet Sí, una vez que obtenga la respuesta, eliminaré uno de estos dos.
Grijesh Chauhan
1
@GrijeshChauhan déjame preguntarte esto: ¿Cuál es mejor hacer tus propias ondas visibles? ¿Tirar piedras al océano o arrojar rocas a un pequeño estanque? Ir directamente a los expertos seguramente le dará la mejor respuesta, y este tipo de pregunta es tan importante (temas avanzados de la base de datos) que le hemos dado su propio sitio en la red. Pero no voy a evitar que preguntes dónde quieres, esa es tu prerrogativa. Mi prerrogativa es votar para moverlo a otro sitio si creo que es a donde pertenece. : D Ambos usamos la red como mejor nos
parece
1
@jcolebrand: En realidad fue solo mi culpa. Solía ​​publicar preguntas en varios lados para obtener una respuesta mejor, rápida y múltiple. It my experience Siempre recibí una mejor respuesta por parte de expertos . Y creo que fue una mejor decisión trasladar la pregunta a los administradores de bases de datos. En todos los casos, estoy muy agradecido con stackoverflow y las personas que están activas aquí. Realmente obtuve una solución para muchos problemas que fueron muy difíciles de encontrar o de cualquier otra web.
Grijesh Chauhan

Respuestas:

5

Ya abordé algo de esta naturaleza usando Procedimientos almacenados: encontrar el nivel más alto de un campo jerárquico: con vs sin CTE (24 de octubre de 2011)

Si miras en mi publicación, puedes usar las funciones GetAncestry y GetFamilyTree como modelo para atravesar el árbol desde cualquier punto dado.

ACTUALIZACIÓN 2012-12-11 12:11 EDT

Volví a mirar mi código desde mi publicación . Escribí la función almacenada para usted:

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 $$

En realidad funciona Aquí hay una muestra:

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>

Pero hay una sola trampa. Agregué una fila extra para el propietario

  • El propietario tiene SSN 0
  • El dueño es su propio jefe con MSSN 0

Aquí están los datos.

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
fuente
entendió la idea!
Grijesh Chauhan
¿Cómo puedo adaptarme para obtener todos los descendientes de Aesta manera 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
¿también maneja multinodos? ya que está colgando en mi base de datos donde se encontraron múltiples nodos de un padre
عثمان غني
3

Lo que está utilizando se llama Modelo de lista de adyacencia . Tiene muchas limitaciones. Serás un problema cuando quieras eliminar / insertar un nodo en un lugar específico. Es mejor que use el modelo de conjunto anidado .

Hay una explicación detallada . Lamentablemente, el artículo en mysql.com ya no existe.

Shiplu Mokaddim
fuente
55
" tiene muchas limitaciones ", pero solo cuando se usa MySQL. Casi todos los DBMS admiten consultas recursivas (MySQL es uno de los pocos que no lo hacen) y eso hace que el modelo sea realmente fácil de manejar.
a_horse_with_no_name
@a_horse_with_no_name Nunca usé nada más que MySQL. Entonces nunca lo supe. Gracias por la información.
Shiplu Mokaddim