Wie sortiere ich die Ergebnisse einer rekursiven Abfrage in einer erweiterten baumartigen Weise?


12

Nehmen wir an, Sie haben eine nodesTabelle wie diese:

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

Es stellt eine knotenähnliche Standardbaumstruktur mit Wurzelknoten oben und mehreren untergeordneten Knoten dar, die an Wurzelknoten oder anderen untergeordneten Knoten baumeln.

Fügen wir ein paar Beispielwerte ein:

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

Jetzt möchte ich die ersten 10 Wurzelknoten und alle ihre Kinder bis zu einer Tiefe von 4 abrufen:

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;

Das funktioniert super und gibt mir folgendes Ergebnis:

 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

Wie Sie vielleicht bemerkt haben, gibt es keine ORDER BYKlausel, daher ist die Reihenfolge nicht definiert. Die Reihenfolge, die Sie hier sehen, ist von Wurzelknoten zu tieferen Knoten.

Wie würde ich die Ergebnisse so anordnen, wie sie in einer erweiterten Baumansicht angezeigt würden, wie Sie aus dem folgenden Beispielbild sehen können?

Erweiterte Baumansicht von Knoten

Grundsätzlich möchte ich, dass die untergeordneten Knoten direkt hinter den entsprechenden übergeordneten Knoten platziert werden. Wenn zwei oder mehr untergeordnete Knoten denselben übergeordneten Knoten haben, sollen sie nach ihrem Zeitstempel sortiert werden. Basierend auf dem obigen Beispiel ist hier die gewünschte Ausgabereihenfolge, die ich erreichen möchte:

 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

Kann mir da jemand erklären, woher die depthKolumne kommt? Ich sehe es nicht in der anfänglichen Tabellenstruktur.
Sorin

@sorin, ich weiß, dass dies ein echter alter Post ist, aber ich bin nur in Google darauf gestoßen und dachte, ich würde deine Frage beantworten. Die Tiefe ergibt sich aus dem Alias ​​der wörtlichen '1' in der ersten Abfrage.
Sam

Antworten:


11

Ein Array, das den Pfad von der Wurzel bis zum Blatt darstellt, sollte die gewünschte Sortierreihenfolge erreichen:

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;

Wenn zwei oder mehr untergeordnete Knoten denselben übergeordneten Knoten haben, sollen sie nach ihrem Zeitstempel sortiert werden.

Verschieben Sie den Pfad um eins zur Wurzel und ordnen Sie ihn zusätzlich um diese Spalte an:

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;

Danke, es funktioniert super! Was ist jedoch mit dem Teil "Wenn zwei oder mehr untergeordnete Knoten denselben übergeordneten Knoten haben, möchten wir, dass sie nach ihrem Zeitstempel sortiert werden"? Ist das mit diesem Ansatz machbar? Es ist möglicherweise nicht immer der Fall, dass eine höhere Knoten-ID einem späteren Zeitpunkt entspricht.
JohnCand

@JohnCand: Du kannst den Pfad um eins zur Wurzel verschieben (wiederhole den Wurzelknoten an erster Stelle!) Und zusätzlich nach dieser Spalte ordnen ...
Erwin Brandstetter
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.