¿Cómo clasifico los resultados de una consulta recursiva en forma de árbol expandido?


12

Supongamos que tiene nodestablas como esta:

CREATE TABLE nodes
(
    node serial PRIMARY KEY,
    parent integer NULL REFERENCES nodes(node),
    ts timestamp NOT NULL DEFAULT now()
);

Representa una estructura de árbol similar a un nodo estándar con nodos raíz en la parte superior y varios nodos secundarios que cuelgan de los nodos raíz u otros nodos secundarios.

Insertemos un par de valores de ejemplo:

INSERT INTO nodes (parent)
VALUES (NULL), (NULL), (NULL), (NULL), (1), (1), (1), (1), (6), (1)
     , (6), (9), (6), (6), (3), (3), (3), (15);

Ahora quiero recuperar los primeros 10 nodos raíz y todos sus hijos hasta una profundidad de 4:

WITH RECURSIVE node_rec AS
(
    (SELECT 1 AS depth, * FROM nodes WHERE parent IS NULL LIMIT 10)

    UNION ALL

    SELECT depth + 1, n.*
    FROM nodes AS n JOIN node_rec ON (n.parent = node_rec.node)
    WHERE depth < 4
)
SELECT * FROM node_rec;

Esto funciona muy bien y me da el siguiente resultado:

 depth | node | parent 
-------+------+--------
     1 |  1   |
     1 |  2   |
     1 |  3   |
     1 |  4   |
     2 |  5   |  1
     2 |  6   |  1
     2 |  7   |  1
     2 |  8   |  1
     2 | 10   |  1
     2 | 15   |  3
     2 | 16   |  3
     2 | 17   |  3
     3 |  9   |  6
     3 | 11   |  6
     3 | 13   |  6
     3 | 14   |  6
     3 | 18   | 15
     4 | 12   |  9

Como habrás notado, no hay ORDER BYcláusula, por lo que el orden no está definido. El orden que ve aquí es de nodos raíz a nodos más profundos.

¿Cómo ordenaría los resultados tal como aparecerían en una vista de árbol expandida, como puede ver en la imagen de ejemplo a continuación?

Vista de árbol ampliada de nodos

Básicamente quiero que los nodos secundarios se coloquen justo después de su nodo principal correspondiente. Si dos o más nodos secundarios tienen el mismo nodo primario, quiero que se ordenen por su marca de tiempo. Según el ejemplo anterior, aquí está el orden de salida deseado que estoy tratando de lograr:

 depth | node | parent | ts
-------+------+--------+---------
     1 |  1   |        | 2014-01-01 00:00:00
     2 |  5   |     1  | 2014-01-01 00:10:00
     2 |  6   |     1  | 2014-01-01 00:20:00
     3 |  9   |     6  | 2014-01-01 00:25:00
     4 |  12  |     9  | 2014-01-01 00:27:00
     3 |  11  |     6  | 2014-01-01 00:26:00
     3 |  13  |     6  | 2014-01-01 00:30:00
     3 |  14  |     6  | 2014-01-01 00:36:00
     2 |  7   |     1  | 2014-01-01 00:21:00
     2 |  8   |     1  | 2014-01-01 00:22:00
     2 |  10  |     1  | 2014-01-01 00:23:00
     1 |  2   |        | 2014-01-01 00:08:00
     1 |  3   |        | 2014-01-01 00:09:00
     2 |  15  |     3  | 2014-01-01 10:00:00
     3 |  18  |     15 | 2014-01-01 11:05:00
     2 |  16  |     3  | 2014-01-01 11:00:00
     2 |  17  |     3  | 2014-01-01 12:00:00
     1 |  4   |        | 2014-01-01 00:10:00

¿Alguien puede explicarme de depthdónde viene la columna? No lo veo en la estructura de la tabla inicial.
sorin

@sorin, sé que esta es una publicación muy antigua, pero me la encontré en Google y pensé en responder a tu pregunta. La profundidad proviene del alias del literal '1' en la primera consulta.
Sam

Respuestas:


11

Una matriz que representa la ruta desde la raíz hasta la hoja debe lograr el orden de clasificación deseado:

WITH RECURSIVE node_rec AS (
   (SELECT 1 AS depth, ARRAY[node] AS path, *
    FROM   nodes
    WHERE  parent IS NULL
    LIMIT  10
   )    
    UNION ALL
    SELECT r.depth + 1, r.path || n.node, n.*
    FROM   node_rec r 
    JOIN   nodes    n ON n.parent = r.node
    WHERE  r.depth < 4
)
SELECT *
FROM   node_rec
ORDER  BY path;

Si dos o más nodos secundarios tienen el mismo nodo primario, quiero que se ordenen por su marca de tiempo.

Desplace la ruta de uno en uno hacia la raíz y ordene por esa columna adicionalmente:

WITH RECURSIVE node_rec AS (
   (SELECT 1 AS depth, ARRAY[node] AS path, *
    FROM   nodes
    WHERE  parent IS NULL
    LIMIT  10
   )    
    UNION ALL
    SELECT r.depth + 1, r.path || n.parent, n.*
    FROM   node_rec r 
    JOIN   nodes    n ON n.parent = r.node
    WHERE  r.depth < 4
)
SELECT *
FROM   node_rec
ORDER  BY path, ts;

Gracias, funciona muy bien! Sin embargo, ¿qué pasa con la parte "Si dos o más nodos secundarios tienen el mismo nodo primario, quiero que se ordenen por su parte de marca de tiempo"? ¿Es esto factible con este enfoque? Puede que no siempre sea el caso que una ID de nodo superior corresponda a un momento posterior.
JohnCand

@ JohnCand: puede cambiar la ruta de uno en uno hacia la raíz (repita el nodo raíz en la primera posición) y ordenar por esa columna adicionalmente ...
Erwin Brandstetter
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.