Wie sortieren Sie einen Baum, der mit dem verschachtelten Mengenmodell gespeichert wurde?

When I refer to nested set model I mean what is described here.

Ich muss ein neues System zum Speichern von "Kategorien" (ich kann mir kein besseres Wort dafür denken) in einer benutzerdefinierten Hierarchie erstellen. Da das Nested-Set-Modell für Lesevorgänge anstelle von Schreibvorgängen optimiert ist, entschied ich mich, das zu verwenden. Leider stieß ich bei meinen Untersuchungen und Tests von verschachtelten Mengen auf das Problem, wie ich den hierarchischen Baum mit sortierten Knoten anzeigen kann. Zum Beispiel, wenn ich die Hierarchie habe:

root
    finances
        budgeting
            fy08
    projects
        research
        fabrication
        release
    trash

Ich möchte, dass es so sortiert wird, dass es wie folgt aussieht:

root
    finances
        budgeting
            fy08
    projects
        fabrication
        release
        research
    trash

Beachten Sie, dass die Herstellung vor der Forschung erscheint.

Wie auch immer, nach einer langen Suche sah ich eine Antwort wie "den Baum in einem mehrdimensionalen Array speichern und sortieren" und "den Baum wieder serialisieren und zurück in dein verschachteltes Mengenmodell" (ich paraphrasiere ...). So oder so, die erste Lösung ist eine schreckliche Verschwendung von RAM und CPU, die beide sehr begrenzte Ressourcen sind ... Die zweite Lösung sieht einfach nach viel schmerzhaftem Code aus.

Ungeachtet dessen war ich in der Lage, herauszufinden, wie (mithilfe des geschachtelten Mengenmodells):

  1. Starten Sie eine neue Struktur in SQL
  2. Fügen Sie einen Knoten als untergeordnetes Element eines anderen Knotens in tree
  3. ein
  4. Fügen Sie einen Knoten nach einem gleichgeordneten Knoten in der Struktur
  5. ein
  6. Ziehen Sie den gesamten Baum mit der Hierarchiestruktur aus SQL
  7. Ziehen Sie einen Teilbaum von einem bestimmten Knoten (einschließlich Root) in der Hierarchie mit oder ohne Tiefenbegrenzung
  8. Suchen Sie den übergeordneten Knoten eines Knotens in der Struktur

Also dachte ich, dass # 5 und # 6 verwendet werden könnten, um die von mir gewünschte Sortierung durchzuführen, und es könnte auch verwendet werden, um den Baum in sortierter Reihenfolge wieder aufzubauen.

Jetzt, da ich mir all diese Dinge angesehen habe, die ich gelernt habe, sehe ich, dass # 3, # 5 und # 6 zusammen verwendet werden können, um sortierte Einsätze durchzuführen. Wenn ich sortierte Inserts gemacht habe, wird es immer sortiert. Wenn ich jedoch die Sortierkriterien ändere oder eine andere Sortierreihenfolge möchte, bin ich wieder auf Platz eins.

Könnte dies nur die Einschränkung des geschachtelten Mengenmodells sein? Wird die Sortierung der Ausgabe bei der Abfrage verhindert?

17

8 Antworten

Ich habe Nested Sets oft verwendet und ich habe oft das selbe Problem gehabt. Was ich tue und was ich empfehlen würde, ist, die Artikel in der Datenbank einfach nicht zu sortieren. Sortieren Sie sie stattdessen in der Benutzeroberfläche. Nachdem Sie alle Knoten aus der Datenbank abgerufen haben, müssen Sie diese wahrscheinlich in eine hierarchische Datenstruktur konvertieren. Sortieren Sie in dieser Struktur alle Arrays, die die untergeordneten Elemente des Knotens enthalten.

Wenn Ihr Frontend beispielsweise eine Flex-App ist und die untergeordneten Elemente eines Knotens in einer ICollectionView gespeichert sind, können Sie die Eigenschaft sort verwenden, um sie so anzuzeigen, wie Sie es möchten.

Ein anderes Beispiel: Wenn Ihr Frontend eine Ausgabe aus einem PHP-Skript ist, könnten Sie die untergeordneten Elemente jedes Knotens in einem Array haben und die Array-Sortierfunktionen von PHP verwenden, um Ihre Sortierung durchzuführen.

Das funktioniert natürlich nur, wenn Sie nicht die eigentlichen db-Einträge sortiert haben müssen, oder?

4
hinzugefügt

Ich denke, das ist in der Tat eine Einschränkung des Nested-Set-Modells. Sie können die untergeordneten Knoten nicht einfach innerhalb ihres jeweiligen übergeordneten Knotens sortieren, da die Reihenfolge der Ergebnismenge wesentlich für die Rekonstruktion der Baumstruktur ist.

Ich denke, es ist wahrscheinlich der beste Ansatz, um den Baum beim Einfügen, Aktualisieren oder Löschen von Knoten zu sortieren. Dies macht sogar Abfragen sehr schnell, was eines der Hauptziele dieser Datenstruktur ist. Wenn Sie gespeicherte Prozeduren für alle Operationen implementieren, ist es sehr einfach zu verwenden.

Sie können auch die Sortierreihenfolge eines vorsortierten Baums umkehren. Sie müssen nur ORDER BY node.rgt DESC anstelle von ORDER BY node.lft ASC verwenden.

Wenn Sie wirklich ein anderes Sortierkriterium unterstützen müssen, können Sie es möglicherweise implementieren, indem Sie jedem Knoten einen zweiten lft und rgt Index hinzufügen und ihn nach den anderen Kriterien sortieren Einfügen/Aktualisieren/Löschen.

4
hinzugefügt

Ich habe gerade das folgende geschrieben, was mir beim Sortieren eines ganzen verschachtelten Set-Baums hilft.

The sort (ideally) requires a view that lists the current level of each node in the tree and a procedure for swapping two nodes - both are included below, the sibling swap code comes from Joe Celkos ' Tree & Hierarchies' book which I strongly recommend to anyone using nested sets.

Die Sortierung kann in der Anweisung 'INSERT INTO @t' geändert werden, hier ist es eine einfache alphanumerische Sortierung für 'Name'

Dies mag ein schlechter Weg sein, es zu tun, insbesondere mit dem Cursor für Set-basierten Code, aber wie ich sage, es funktioniert für mich, hoffe, es hilft.

UPDATE:

Code unten zeigt jetzt Version ohne cusor. Ich sehe etwa 10-fache Geschwindigkeitssteigerungen

CREATE VIEW dbo.tree_view

AS

SELECT t2.NodeID,t2.lft,t2.rgt ,t2.Name, COUNT(t1.NodeID) AS level  
FROM dbo.tree t1,dbo.tree t2
WHERE t2.lft BETWEEN t1.lft AND t1.rgt
GROUP BY t2.NodeID,t2.lft,t2.rgt,t2.Name

GO

----------------------------------------------

  DECLARE @CurrentNodeID int
DECLARE @CurrentActualOrder int
DECLARE @CurrentRequiredOrder int
DECLARE @DestinationNodeID int
DECLARE @i0 int
DECLARE @i1 int
DECLARE @i2 int
DECLARE @i3 int

DECLARE @t TABLE (TopLft int,NodeID int NOT NULL,lft int NOT NULL,rgt int NOT NULL,Name varchar(50),RequiredOrder int NOT NULL,ActualOrder int NOT NULL)


INSERT INTO @t (toplft,NodeID,lft,rgt,Name,RequiredOrder,ActualOrder)
    SELECT tv2.lft,tv1.NodeID,tv1.lft,tv1.rgt,tv1.Name,ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.ColumnToSort),ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.lft ASC)
    FROM dbo.tree_view tv1 
    LEFT OUTER JOIN dbo.tree_view tv2 ON tv1.lft > tv2.lft and tv1.lft < tv2.rgt and tv1.level = tv2.level+1
    WHERE tv2.rgt > tv2.lft+1

    DELETE FROM @t where ActualOrder = RequiredOrder


WHILE EXISTS(SELECT * FROM @t WHERE ActualOrder <> RequiredOrder)
BEGIN


    SELECT Top 1 @CurrentNodeID = NodeID,@CurrentActualOrder = ActualOrder,@CurrentRequiredOrder = RequiredOrder
    FROM @t 
    WHERE ActualOrder <> RequiredOrder
    ORDER BY toplft,requiredorder

    SELECT @DestinationNodeID = NodeID
    FROM @t WHERE ActualOrder = @CurrentRequiredOrder AND TopLft = (SELECT TopLft FROM @t WHERE NodeID = @CurrentNodeID) 

    SELECT @i0 = CASE WHEN c.lft < d.lft THEN c.lft ELSE d.lft END,
            @i1 =  CASE WHEN c.lft < d.lft THEN c.rgt ELSE d.rgt END,
            @i2 =  CASE WHEN c.lft < d.lft THEN d.lft ELSE c.lft END,
            @i3 =  CASE WHEN c.lft < d.lft THEN d.rgt ELSE c.rgt END
    FROM dbo.tree c
    CROSS JOIN dbo.tree d
    WHERE c.NodeID = @CurrentNodeID AND d.NodeID = @DestinationNodeID

    UPDATE dbo.tree
    SET lft = CASE  WHEN lft BETWEEN @i0 AND @i1 THEN @i3 + lft - @i1
                    WHEN lft BETWEEN @i2 AND @i3 THEN @i0 + lft - @i2
            ELSE @i0 + @i3 + lft - @i1 - @i2
            END,
        rgt = CASE  WHEN rgt BETWEEN @i0 AND @i1 THEN @i3 + rgt - @i1
                    WHEN rgt BETWEEN @i2 AND @i3 THEN @i0 + rgt - @i2
            ELSE @i0 + @i3 + rgt - @i1 - @i2
            END
    WHERE lft BETWEEN @i0 AND @i3 
    AND @i0 < @i1
    AND @i1 < @i2
    AND @i2 < @i3

    UPDATE @t SET actualorder = @CurrentRequiredOrder where NodeID = @CurrentNodeID
    UPDATE @t SET actualorder = @CurrentActualOrder where NodeID = @DestinationNodeID

    DELETE FROM @t where ActualOrder = RequiredOrder

END
2
hinzugefügt
Super, genau das habe ich gesucht. Es löste das Sortierproblem, das ich mit unserer verschachtelten Mengenhierarchie hatte, vollständig.
hinzugefügt der Autor Hamman359, Quelle

Ja, es ist eine Einschränkung des geschachtelten Mengenmodells, da verschachtelte Mengen eine vorbestellte Darstellung einer Hierarchie sind. Diese Vorbestellung ist der Grund dafür, dass es so schnell zum Lesen ist. Das Adjacency-Modell, das auch auf der Seite beschrieben wird, auf die Sie verlinken, ermöglicht eine flexible Sortierung und Filterung, jedoch mit erheblichen Auswirkungen auf die Performance.

Meine bevorzugte Vorgehensweise für Einfügungen und Verschiebungen in einem verschachtelten Satz besteht darin, den betroffenen Zweig wie im Adjazenzmodell zu behandeln: Eine Liste der neuen Geschwister erhalten; finde den richtigen Platz in der Liste für den neuen Knoten; und konstruiere die erforderlichen update-Anweisungen (das ist das Bit, wo du wirklich vorsichtig sein musst). Was die Änderung Ihrer Bestellkriterien angeht: Es handelt sich um einen einmaligen Batch-Job, also können Sie es sich leisten, etwas RAM und CPU zu blasen. Die flexibelste Antwort wäre, die geschachtelte Mengen-Darstellung in eine Adjazenzdarstellung zu zerlegen und den verschachtelten Satz neu aufzubauen die Nachbarschaft basiert auf neuen Kriterien.

1
hinzugefügt

Das Sortieren von verschachtelten Sets hat keine Grenzen und es ist nicht schwierig. Sortiere einfach nach der LINKEN Laube (Anker, was auch immer) und es ist fertig. Wenn Sie für jeden Knoten einen LEVEL haben, können Sie auch den richtigen Einzug basierend auf dem Level ziehen.

1
hinzugefügt
Das ist der eigentliche Punkt, den ich versuche zu machen (und ich werde den -1-Hit nehmen, um es zu machen ;-). Auch Justins feine Lösung verwendet immer noch eine While-Schleife, die immer noch ein Cursor ohne das Wort CURSOR ist. Der Schlüssel dazu besteht darin, die verschachtelten Sätze zunächst in der richtigen Reihenfolge zu erstellen. Ich könnte ein paar Links dazu schreiben, wie man das richtig und mit genügend Geschwindigkeit macht, dass man es bei jeder Änderung leicht machen könnte, aber ich würde wahrscheinlich einfach nur eine URL anstelle von Code veröffentlichen, wie ich es sch
hinzugefügt der Autor Jeff Moden, Quelle

Ich glaube, dass in Ihrem Fall, in dem die Knoten, die Sie tauschen möchten, keine Nachkommen haben, Sie einfach die lft und rgt Werte umtauschen können. Betrachten Sie diesen Baum:

   A
/  \
B     C
    /\
    D   E

Dies könnte zu dieser Gruppe von verschachtelten Mengen werden:

1 A 10 
2 B 3  
4 C 9
5 D 6
7 E 8

Betrachten Sie nun, dass Sie D und E austauschen möchten. Die folgenden verschachtelten Sätze sind gültig und D und E werden vertauscht:

1 A 10
2 B 3 
4 C 9 
7 D 8
5 E 6 

Das Austauschen von Knoten, die Teilbäume enthalten, kann natürlich nicht auf diese Weise durchgeführt werden, da Sie auch die Lft- und Rgt-Werte der Kinder aktualisieren müssen.

0
hinzugefügt

You can sort thier when you render. I explained rendering here How to render all records from a nested set into a real html tree

0
hinzugefügt

See my simple solution from method of my class. $this->table->order is Nette framework code to get data from DB.

$tree = Array();
$parents = Array();
$nodes = $this->table->order('depth ASC, parent_id ASC, name ASC');
$i = 0;
$depth = 0;
$parent_id = 0;

foreach($nodes as $node) {
    if($depth < $node->depth || $parent_id < $node->parent_id) {
        $i = $parents["{$node->parent_id}"] + 1;
    }
    $tree[$i] = $node;
    $parents["{$node->id}"] = $i;
    $depth = $node->depth;
    $parent_id = $node->parent_id;
    $i += (($node->rgt - $node->lft - 1)/2) + 1;
}
ksort($tree);
0
hinzugefügt