Come ordinare i risultati di una query ricorsiva in modo espanso simile ad un albero?


12

Supponiamo che tu abbia una nodestabella come questa:

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

Rappresenta una struttura ad albero standard simile a un nodo con nodi radice nella parte superiore e diversi nodi figlio che pendono da nodi radice o altri nodi figlio.

Inseriamo un paio di valori di esempio:

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

Ora voglio recuperare i primi 10 nodi root e tutti i loro figli fino a una profondità di 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;

Funziona benissimo e mi dà il seguente risultato:

 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

Come avrai notato, non esiste una ORDER BYclausola, quindi l'ordine non è definito. L'ordine che vedi qui va dai nodi radice a nodi più profondi.

Come posso ordinare i risultati come apparirebbero in una vista ad albero espanso, come puoi vedere dall'immagine di esempio qui sotto?

Vista ad albero espansa dei nodi

In pratica, desidero che i nodi figlio vengano posizionati subito dopo il nodo genitore corrispondente. Se due o più nodi figlio hanno lo stesso nodo genitore, voglio che vengano ordinati in base al loro timestamp. Sulla base dell'esempio sopra, ecco l'ordine di output desiderato che sto cercando di ottenere:

 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

Qualcuno può spiegarmi da dove viene la depthcolonna? Non lo vedo nella struttura iniziale della tabella.
sorin,

@sorin, so che questo è un vero vecchio post, ma mi sono appena imbattuto in Google e ho pensato di rispondere alla tua domanda. La profondità deriva dall'alias del letterale '1' nella prima query.
Sam,

Risposte:


11

Un array che rappresenta il percorso dalla radice fino alla foglia dovrebbe raggiungere il tipo di ordinamento desiderato:

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;

Se due o più nodi figlio hanno lo stesso nodo genitore, voglio che vengano ordinati in base al loro timestamp.

Sposta il percorso di uno verso la radice e ordina ulteriormente per quella colonna:

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;

Grazie, funziona benissimo! Tuttavia, che dire della parte "Se due o più nodi figlio hanno lo stesso nodo principale, voglio che vengano ordinati in base alla loro data e ora"? È possibile con questo approccio? Potrebbe non essere sempre il caso che un ID nodo superiore corrisponda a una volta successiva.
JohnCand

@JohnCand: puoi spostare il percorso di uno verso la radice (ripeti il ​​nodo radice in prima posizione!) E ordina per quella colonna in più ...
Erwin Brandstetter
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.