16. Juni 2008 um 19:31 Uhr von Daniel · Trackback-URL
Anders als bei Parent-Children-Beziehungen muss beim umhängen von Teilbäumen in Nested Sets nicht nur die Parent-Zuweisung des zu verschiebenden Containers geändert werden, sondern im gesamten Baum ändern sich an mehreren Stellen die Werte von einzelnen Ordner.
Das verschieben von Teilbäumen ist sehr komplex. Es muss unterschieden werden, wohin ein Container verschoben wird:
- Container wird unter einen seiner Eltern-Container verschoben
- Container wird in einen vorherigen Teilbaum verschoben
- Container wird in einen nachfolgenden Teilbaum verschoben

Zuerst müssen die LFT- und RGT-Werte des zu verschiebenden Containers B und des Ziel-Containers A ausgelesen werden. Zusätzlich lesen wir bereits hier aus wieviele Kinder ein Container hat.
Auslesen der LFT/RGT-Werte und Kinder von Container A und B
-- Auslesen von Container A SELECT lft, rgt, ROUND((rgt - lft - 1) / 2) AS children FROM tabelle_container WHERE id = $parent_id; -- Auslesen von Container B SELECT lft, rgt, ROUND((rgt - lft - 1) / 2) AS children FROM tabelle_container WHERE id = $id;
Damit haben wir die Werte mit denen wir nun den gesamten Baum neu erzeugen bzw. die LFT- und RGT-Werte aktualisieren können. Sie werden in die Variablen $a_lft, $a_rgt, $a_children und $b_lft, $b_rgt, $b_children gespeichert. Wichtig ist noch ein zusätzliches Feld “updated” in der Datenbank-Tabelle. Dort wird während des Update-Vorgangs festgelegt, ob ein Container bereits aktualisiert wurde oder nicht. Andernfalls könnte es bei einem nachfolgenden SQL-Update passieren, dass schon neu erstellte Werte erneut überschrieben werden.
Nun muss unterschieden werden, welche Art von Verschiebung vorgenommen werden soll (siehe oben).
if( $b_lft > $a_lft && $b_rgt < $a_rgt ) { // Container wird unter einen seiner Eltern-Container verschoben. } elseif( $b_lft > $a_lft && $b_rgt > $a_rgt ) { // Container wird unter einen links liegenden Container verschoben. } else { // Container wird unter einen rechts liegenden Container verschoben. }
Bsp. 1:
Der Container “Grün” soll unter den Container “Pflanze” verschoben werden. Es findet damit im Baum lediglich eine Verschiebung nach oben innerhalb einer Wurzel statt. Die erste if-Bedingung trifft also zu und mit den folgenden SQL-Updates wird der Baum neu generiert.
-- Update Container B (Container der verschoben wird) UPDATE tabelle_container SET lft = $a_rgt - ( ($b_children + 1) * 2 ), rgt = $a_rgt - 1, updated = 1 WHERE id = $id; -- Update aller Container unter Container B UPDATE tabelle_container SET lft = lft + ($a_rgt - $b_rgt - 1), rgt = rgt + ($a_rgt - $b_rgt - 1), updated = 1 WHERE lft > $b_lft AND rgt < $b_rgt AND updated != 1; -- Update aller Container von B nach oben zum Root UPDATE tabelle_container SET rgt = rgt - ( ($b_children + 1) * 2 ), updated = 1 WHERE lft < $b_lft AND lft > $a_lft AND rgt > $b_lft AND updated != 1; -- Update aller Container zwischen B und A UPDATE tabelle_container SET lft = lft - ( ($b_children + 1) * 2 ), rgt = rgt - ( ($b_children + 1) * 2 ), updated = 1 WHERE lft > $b_rgt AND rgt < $a_rgt AND updated != 1; -- In allen Containern den Wert von 'updated' auf 0 setzen UPDATE tabelle_container SET updated = 0;
Bsp. 2:
Der Container “Dalie” soll unter den Container “Ahorn” verschoben werden. Es findet damit eine Verschiebung in einen vorherige Ast statt. Die zweite if-Bedingung trifft nun zu und damit werden die folgenden SQL-Updates nötig, um den Baum neu zu generieren.
-- Update Container A (Container unter den verschoben wird) UPDATE tabelle_container SET rgt = rgt + ( ( $b_children + 1 ) * 2 ), updated = 1 WHERE id = $parent_id; -- Update Container B (Container der verschoben wird) UPDATE tabelle_container SET lft = $a_rgt, rgt = ( $a_rgt - 1 + ( ( $b_children + 1 ) * 2 ) ), parent_id = $parent_id, updated = 1 WHERE id = $id; -- Update aller Container unter Container B UPDATE tabelle_container SET lft = lft - ( $b_lft - $a_rgt ), rgt = rgt - ( $b_lft - $a_rgt ), updated = 1 WHERE lft > $b_lft AND rgt < $b_rgt AND updated != 1; -- Update aller Container zwischen B und A UPDATE tabelle_container SET lft = lft + ( ( $b_children + 1 ) * 2 ), rgt = rgt + ( ( $b_children + 1 ) * 2 ), updated = 1 WHERE lft > $a_rgt AND rgt < $b_lft AND updated != 1; -- Update aller Container von A nach oben zum Root UPDATE tabelle_container SET rgt = rgt + ( ( $b_children + 1 ) * 2 ), updated = 1 WHERE lft < $a_lft AND rgt > $a_rgt AND rgt < $b_lft AND updated != 1; -- Update aller Container von B nach oben zum Root UPDATE tabelle_container SET lft = lft + ( ( $b_children + 1 ) * 2 ), updated = 1 WHERE lft > $a_rgt AND lft < $b_lft AND rgt > $b_rgt AND updated != 1; -- In allen Containern den Wert von 'updated' auf 0 setzen UPDATE tabelle_container SET updated = 0;
Bsp. 3:
Der Container “Grün” soll unter den Container “Blume” verschoben werden. Im Baum wird also ein Container unter einen nachfolgenden Container verschoben und die else-Bedingung trifft zu. Mit diesen SQL-Updates wird der neue Baum generiert.
-- Update Container A (Container unter den verschoben wird) UPDATE tabelle_container SET lft = lft - ( ( $b_children + 1 ) * 2 ), updated = 1 WHERE id = $parent_id; -- Update Container B (Container der verschoben wird) UPDATE tabelle_container SET lft = $a_rgt - ( ( $b_children + 1 ) * 2 ), rgt = $a_rgt - 1, parent_id = $parent_id, updated = 1 WHERE id = $id; -- Update aller Container unter Container B UPDATE tabelle_container SET lft = lft + ( ( $a_rgt - 1 ) - $b_rgt ), rgt = rgt + ( ( $a_rgt - 1 ) - $b_rgt ), updated = 1 WHERE lft > $b_lft AND rgt < $b_rgt AND updated != 1; -- Update aller Container von B nach oben zum Root UPDATE tabelle_container SET rgt = rgt - ( ( $b_children + 1) * 2 ), updated = 1 WHERE lft < $b_lft AND rgt > $b_rgt AND rgt < $a_lft AND updated != 1; -- Update aller Container zwischen B und A UPDATE tabelle_container SET lft = lft - ( ( $b_children + 1 ) * 2 ), rgt = rgt - ( ( $b_children + 1 ) * 2 ), updated = 1 WHERE lft > $b_rgt AND rgt < $a_rgt AND updated != 1; -- Update aller Container von A nach oben zum Root UPDATE tabelle_container SET lft = lft - ( ( $b_children + 1 ) * 2 ), updated = 1 WHERE lft > $b_rgt AND lft < $a_rgt AND rgt > $a_rgt AND updated != 1; -- In allen Containern den Wert von 'updated' auf 0 setzen UPDATE tabelle_container SET updated = 0;
Anmerkung zum verschieben
Es gibt in der if-Abfrage nur SQL-Statements für Verschiebungen nach oben, nach links und nach rechts. Für das verschieben nach unten gibt es keine Statements, da man einen Container nicht unter sich selbst verschieben kann.
Wer auf Papier üben und das ganze nachvollziehen will, kann sich dieses Dokument speichern und ausdrucken.
Hallo aus Duisburg.
Ich beschäftige mich erst kürzlich mit Nested Sets.
Beim Anwender der Beispiele zum Verschieben von Teilästen usw. (http://code.reduxo.de/nested-sets-teilbaeume-verschieben)
fiel mir auf das ich dieSpalte “parent_id” nicht in meiner DB-Table habe.
Ich verwende lediglich lft,rgt,eine id (auto increment) und eine Spalte für die Bezeichnung.
Können Sie mir auf die Sprünge helfen?
Hallo Daniel,
Ich konnte Deinen bestehenden Code schon erfolgreich (nach ersten Tests) in meine bestehende Implemtierung einbinden.
hast du auch eine Implemtierung bzw. Erweiterung Deiner bestehenden Implementierung, welche das Verschieben von Teilbäumen auf einen neue Position in derselben Tiefe ermöglicht?
Also sowas:
0
|
–00
|
–01
==>
0
|
–01
|
–00
Danke in jedem Fall schon für diesen Super-Artikel!
PROBLEM: leider ist ein korrekter post des Codes nicht möglich, da kleiner-als-zeichen wohl als öffnende tags interpretiert werden und den code zerstückeln.
tut mir leid. evtl. poste ich den code in einem gesonderten artikel.
Kann es sein, dass $a_children gar nicht benötigt wird? Oder ist irgendwo ein Schreibfehler (und es müsste “$a_children” statt “$b_children” heißen)?
Ich habs gerade mal in meine NestedSet-Klasse eingebaut (und musste halt sämtliche Sachen mit Platzhaltern versehen), und es klappt nur so halb. Hab denk ich mal sicher was übersehen oder beim copy/paste verbockt, da hier ja sonst scheinbar keiner Probleme damit hat. Das mit dem $a_children hat mich nur stutzig gemacht…
Super Sache dennoch! Vielen Dank!!
Ich muss mich korrigieren, es funktioniert nach etwas testen tadellos
$a_children scheint man also wirklich nicht zu brauchen.
Viele Grüße
Nested Sets bei familie-barnim.de…
Es war lange überfällig, und nun ist es endlich vollbracht: Die Kategorie-Struktur auf dem Barnimer Familienportal wurde auf Nested Sets umgestellt. Diese “kleine” Strukturänderung bringt gleich mehrere Vorteile: Das Portal wird wesent…
Hallo Daniel,
danke für diesen hilfreichen POST. Ich habe deine sourcen mal für meine Anwendung benutzt. Mir ist aufgefallen, dass du im BSP. 1 (Cont. Grün unter Cont. Pflanze verschieben) im ersten SQL-Statement die parent_id nicht anpasst. Für diesen Container, der ja nach oben verschoben wird, ändert sich doch auch die parent_id.
Liege ich richtig, oder habe ich einen Gedankenfehler?
Danke für deine Antwort.
Hi Daniel,
danke für den Code.
Ich habe ihn soweit implementiert und angepasst, allerdings bekommen ich bei manchen “Verschiebungen” Fehlermeldungen aufgrund eines doppelten Primärschlüssels. Ich habe “lft’ und “rgt” als Primärschlüssel gewählt.
Wenn ich keinen Primärschlüssel in der Tabelle habe funktioniert alles prima, es gibt “lft” und “rgt” danach auch nicht doppelt.
Kann es sein das mein gewählter PK während der Aktualisierungen im Code temporär doppelt vorkommt?
Danke und Gruß
Hallo,
falls jemand nach Wegen sucht, diese Verschiebeoperationen mit einem einzigen (My)SQL-Update-Statement durchzuführen, habe ich dazu einen Blog-Eintrag (http://www.php-resource.de/forum/blogs/amicanoctis/25-nested-set-move-subtree.html) verfasst. Er beinhaltet das Verschieben eines beliebigen Teilbaums in einen beliebigen anderen Knoten und das Vertauschen zweier Teilbäume.
Hoffe, es hilft jemandem.
Beste Grüße,
Amica
Danke!
Habe NestedSets dringend für mein derzeitiges Projekt (in Java) gebraucht und diese Hilfe, respektive dieses Tutorial dazu genutzt, mir eine Klasse (auch für spätere Projekte) zu schreiben.
Funktioniert, nachdem ich nach zwei Tagen einen kleinen Fehler in meinem Source gefunden habe, blendend.
Jetzt muss ich mal schauen, was ich noch einbauen muss, damit der Komfort gegeben ist, den ich mir vorstelle.
Grüße aus dem Spessart
Jeanot Bruchmann
Hallo,
vielen Dank für diesen Code, er hat mir sehr geholfen !
Ich muss mich jedoch Lars anschließen: gibt es eine Möglichkeit, einen Teilbaum auf der gleichen Ebene zu verschieben ? Das wäre auf jeden Fall noch die Krönung…
Vielen Dank nochmal,
Jan
Der Artikel ist mit den folgenden Schlagworten versehen:
MySQL, Nested Sets
Es gibt derzeit 15 Kommentare zu diesem Eintrag. Wenn Du dazu auch etwas sagen möchtest, dann gib Deinen Senf hier ab.
Du kannst Dir auch die Antworten auf diesen Beitrag als RSS-Feed abonnieren.