Comment trier les résultats d'une requête récursive de manière arborescente étendue?


12

Supposons que vous ayez des nodestables comme celle-ci:

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

Il représente une structure arborescente de type nœud standard avec des nœuds racine en haut et plusieurs nœuds enfants qui pendent des nœuds racine ou d'autres nœuds enfants.

Insérons quelques exemples de valeurs:

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

Maintenant, je veux récupérer les 10 premiers nœuds racine et tous leurs enfants jusqu'à une profondeur 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;

Cela fonctionne très bien et me donne le résultat suivant:

 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

Comme vous l'avez peut-être remarqué, il n'y a pas de ORDER BYclause, donc l'ordre n'est pas défini. L'ordre que vous voyez ici va des nœuds racine aux nœuds plus profonds.

Comment puis-je classer les résultats tels qu'ils apparaissent dans une arborescence développée, comme vous pouvez le voir sur l'exemple ci-dessous?

Vue arborescente étendue des nœuds

Je veux essentiellement que les nœuds enfants soient placés juste après leur nœud parent correspondant. Si deux nœuds enfants ou plus ont le même nœud parent, je veux qu'ils soient triés par leur horodatage. Sur la base de l'exemple ci-dessus, voici l'ordre de sortie souhaité que j'essaie d'atteindre:

 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

Quelqu'un peut-il m'expliquer d'où vient la depthcolonne? Je ne le vois pas dans la structure de table initiale.
sorin

@sorin, je sais que c'est un vrai vieux post, mais je suis juste tombé dessus dans Google et j'ai pensé répondre à votre question. La profondeur provient de l'alias du littéral «1» dans la première requête.
Sam

Réponses:


11

Un tableau représentant le chemin de la racine jusqu'à la feuille doit atteindre l'ordre de tri souhaité:

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 deux nœuds enfants ou plus ont le même nœud parent, je veux qu'ils soient triés par leur horodatage.

Décalez le chemin de un vers la racine et triez en plus par cette colonne:

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;

Merci, ça marche super! Cependant, qu'en est-il de la partie "Si deux nœuds enfants ou plus ont le même nœud parent, je veux qu'ils soient triés par leur horodatage"? Est-ce faisable avec cette approche? Il n'est pas toujours possible qu'un ID de nœud supérieur corresponde à une date ultérieure.
JohnCand

@JohnCand: Vous pouvez déplacer le chemin de un vers la racine (répétez le nœud racine en première position!) Et ordonner par cette colonne en plus ...
Erwin Brandstetter
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.