NestedSet - Algorithmus gesucht

Traue mich kaum zu fragen ;)
Aber kann mir jemand erklären, was NetsedSets sind?

Habe soetwas noch nie gehört und kein ordentliche Erklärung via http://www.g00gl3.de gefunden.
(Vieleicht habe ich nicht lange genug gesucht...) :rolleyes:

Vieleicht kann das hier ja jemand mit einigen Worten erklären...
 
Ich höre davon direkt auch das erstmal, aber soweit ich mich mit SQL und Bäumen auskenne ist das eine Methode um aus SQL Bäume erstellen zu können ohne eine rekursive unktion zu nutzen, welches einen erheblichen Geschwindigkeitsvorteil hat!
Diese Bäume kann man dann beliebig nutzen und mit ihnen Arbeiten.
Ich denke mir, dass das sehr gut sein könnte wenn man einen Explorer machen will, der sich dann für alles mögliche nutzen lässt... einfachstes Beispiel wäre ein Stammbaum deiner familie der bis 2000 jahre zurück geht und über 5000 Verwandte fasst (nur ein beispiel)

ich hoffe ich habe mich nicht zu weit aus dem Fenster gehängt, denn NestedSet höre ich vom begriff das erste mal.
 
Johannes Röttger hatte weiter oben schon einen Link zu einem guten Tutorial gepostet: http://develnet.org/36.html

NestedSet sind Bäume in SQL. Sie sind performanter als das Pfad Modell und erst recht als das Parent Child Modell.

Bei Nested Sets speicherst du zu jedem Knoten des Baumes 2 ID Werte: left und right.
Beim Wurzelknoten ist left per Definition = 1 und right = 2, wenn der Knoten keine Kinder enthält. Kommt nun ein Kind dazu, umschließen die left und right Werte der Eltern die der Kinder.

Kommt also zu unserem Knoten ein Kind hinzu, hat es für left = 2 und für right = 3. Die Elternrubrik hat nun aber einen right Wert von 4, es soll ja das Kind umschließen.

Du siehst hier vielleicht schon den Hasenfuß an der Sache: Lösch mal einen Knoten. Dann kann es im ungünstigsten Fall dazu kommen, dass alle anderen Knoten angepasst werden müssen. Aber dafür gibt es ja Klassen wie CB_NestedSet oder PEAR::DB_NestedSet :)

Das gute an Nested Sets sind die Abfragemöglichkeiten. Ich bekomme mit EINEM Query alle Kinder eines bestimmten Zweiges. Bei Parent Child muss du für jeden Zweig ein einzelnes Query absetzen, um an dessen Kinder zu kommen.

Genau das gleiche Problem haben wir beim Parent Child Modell bei der Abfrage der Eltern. Da muss ich rekursiv jedes Level durchlaufen, wenn ich den Pfad von der Wurzel bis zum aktuellen Knoten haben will. Bei Nested Sets reicht hier wieder ein einziges ;)

Überzeugt? Schaus dir mal an, das ist echt genial das System.

Ciao, Jörg
 
Razorhawk:

Du kannst das auch für Foren nehmen um ein Threaded Forum zu kreieren (also nicht wie hier, ein Flat Forum).

Bei Content*Builder werden natürlich jetzt die Rubriken per NestedSets angelegt aber ich habe auch eine Erweiterung zur Verwaltung von Immobilien. Dort sind die Regionen und die Objekttypen als NestedSets abgelegt.
Da ist das geniale, dass ich nach einer Tabellenverknüpfung zwischen Regionen und Immobilien alle Objekte bekomme, die in einer bestimmten Region liegen. Aber ich muss nur wissen, welches die gewählte Region ist, alle Unterregionen werden dann per Definition mit geladen und ich bekomme auch alle Objekte die darin liegen ohne die Unterregionen explizit abzufragen.

Vielseitig wie man sieht :)

Ciao, Jörg
 
Mal rein theoretisch gesehen.
Dann wäre der dämliche Windows-Explorer ab unf zu wesentlich schneller wenn er diese methode nutzen würde ;)
 
Original geschrieben von F.o.G.
Razorhawk:

Du kannst das auch für Foren nehmen um ein Threaded Forum zu kreieren (also nicht wie hier, ein Flat Forum).

Bei Content*Builder werden natürlich jetzt die Rubriken per NestedSets angelegt aber ich habe auch eine Erweiterung zur Verwaltung von Immobilien. Dort sind die Regionen und die Objekttypen als NestedSets abgelegt.
Da ist das geniale, dass ich nach einer Tabellenverknüpfung zwischen Regionen und Immobilien alle Objekte bekomme, die in einer bestimmten Region liegen. Aber ich muss nur wissen, welches die gewählte Region ist, alle Unterregionen werden dann per Definition mit geladen und ich bekomme auch alle Objekte die darin liegen ohne die Unterregionen explizit abzufragen.

Vielseitig wie man sieht :)

Ciao, Jörg

Da man Threaded-Foren auch Flat darstellen kann, kann man auch Flat-Foren mit NestedSets bastln! :)
Übrigens nette Klasse, vielen Dank! :)
 
da hast du vollkommen recht. Das will ich nämlich demnächst in Angriff nehmen. Man soll dann im Adminitrationsbereich einstellen können, wie das Forum dargestellt werden soll.

Im Userbereich nicht, weil das unter Umständen verwirrend wird, wenn der eine User threaded guckt und der andere flat ;)

Ich liebe Nested Sets. In Content*Builder bau ich die derzeit in die Artikelverwaltung ein, um zum Beispiel Kapitel wie in einem Buch damit realisieren zu können. Auch kann man so Dachartikel zu einem Thema verfassen und andere Authoren können ihre Artikel unter dieses Dach stellen. Das ist zumindest schneller als die Verlinkung über Keywords, wo man ja mit Suchmustern einen erheblichen Geschwindigkeitsverlust hat....

Die Klasse wird immer mit C*B ausgeliefert aber ich werde die Klasse von Zeit zu Zeit aktualisiert ins Download Archiv stellen.

Ciao, Jörg
 
Ach verdammt, ich habe gerade gemerkt, dass im Zip Verzeichnis irgendwas durcheinander gekommen ist. Die KlassenDoku sollte da eigentlich unter doc liegen.....

Naja, egal. Ein Beispiel? Kein Problem:

Nehmen wir diese Tabelle an
Code:
CREATE TABLE menu (
  `name` varchar(255) NOT NULL default '',
  `nodeID` int(10) unsigned NOT NULL default '0',
  `parentID` int(10) unsigned NOT NULL default '0',
  `rootID` int(10) unsigned NOT NULL default '0',
  `leftID` int(10) unsigned NOT NULL default '0',
  `rightID` int(10) unsigned NOT NULL default '0',
  `level` int(10) unsigned NOT NULL default '0',
  `order_num` int(10) unsigned NOT NULL default '0',
  KEY `rootID` (`rootID`),
  KEY `leftID` (`leftID`),
  KEY `rightID` (`rightID`),
  KEY `level` (`level`),
  KEY order_num (`order_num`),
  PRIMARY KEY ( `nodeID` )
) TYPE=MyISAM;

In deiner PHP Datei musst du natürlich erst mal die Klasse einbinden und dann über die Methode factory ableiten (factory pattern deswegen, um das ganze später mal leichter an PEAR::DB oder PEAR::MDB anpassen zu können). Die Methode erwartet mind. 1 Array mit den 7 wichtigen Daten table, id, parent, root, l, r, level und order_num.

Mit diesen 7 Feldern ist es dann auch problemlos möglich, einfach mal PEAR::DB_NestedSet einzusetzen (wenn es mal bugfreier ist).

Das 2. Array ist für die zusätzlichen Felder gedacht, in unserem Beispiel das Feld name. Man soll ja bei der Erschaffung eines Knotens auch solche Werte gleich mit übergeben können. Ich geh einfach mal davon aus, dass man immer mindestens ein Feld hier drin stehen haben wird, nämlich den Namen des Knotens.

In den Array's stehen die Feldnamen auf der rechten Seite, die Aliasnamen auf der linken. Die Aliasnamen im $params Array sind fest und müssen so lauten.

Beispiel:
PHP:
<?php
/*
* Nested Set Klasse einfügen
*/
require_once("CB_NestedSet.class.php");

/* 
* Nested Set Objekt für Typen anlegen 
*/
$params = array (
	'table'   => 'menu',
	'id'      => 'nodeID',
	'parent'  => 'parentID',
	'root'    => 'rootID',
	'l'       => 'leftID',
	'r'       => 'rightID',
	'level'   => 'level',
	'norder'  => 'order_num'
);

$additional = array (
	'name'           => 'name'
);

$nestedSet = CB_NestedSet::factory($params, $additional);
?>

Man hat nun ein NestedSet Objekt.

Beim einfügen eines neuen Knotens muss man unterscheiden, ob man einen Wurzelknoten oder einen Kindknoten erschaffen will.

Man muss die zusätzlichen Felder die zu füllen sind per Array übergeben:
PHP:
$additional = array (
    "name" => "Name des Knotens"
);

Für Wurzelknoten nimmt man:
PHP:
$id = $nestedSet->createRootNode($additional);

Bei Kindknoten nimmt man:
PHP:
$id = $nestedSet->createSubNode($parentID, $additional);

Natürlich ist auch löschen möglich:
PHP:
$id = $nestedSet->deleteNode($nodeID);

So...nun aber zu den Schmankerln meiner Klasse: den Abfrage Methoden.
Es werden ja jetzt schon einige Methoden geboten. Jede Methode die nur einen Knoten abfragt liefert ein Array mit den Feldwerten direkt zurück. Wenn es keinen Knoten gab, wird immer false zurück geliefert.

Beispiel:
PHP:
$node = $nestedSet->getNode($id);
$nodeID = $node[nodeID];
$nodeName = $node[name];

Wie man sieht, muss man die Felder so ansprechen, wie die Tabellennamen in der Datenbank lauten. Im Gegensatz dazu muss man beim Erzeugen der Knoten beim Additional Array immer das als Feldnamen angeben, was man beim Ableiten der Klasse auf der linken Seite angegeben hat.

Man kann dieses Verhalten aber ändern:
PHP:
$node = $nestedSet->getNode($id, true);
$nodeID = $node[id];
$nodeName = $node[name];

Als 2. Parameter wurde hier die Verwendung von Aliasnamen bei den zurück gelieferten Einträgen als Feldnamen auf true gesetzt. Man kann erkennen, dass man nicht mehr nodeID angeben muss, um an die ID zu kommen, sondern den Alias id.

Wenn man eine Methode aufruft, die mehr als einen Knoten zurückliefern kann, bekommt man immer ein Array, dessen erstes Level die ID enthält und im 2. Level die Werte dieses Knotens. Die Knoten werden in der richtigen Reihenfolge geliefert.

Beispiel:

PHP:
$pathNodes = $nestedSet->getPath($id, true);
if($pathNodes != false) {
    $multiple = false;
    foreach($pathNodes as $k => $v) {
        if($multiple) { 
            echo " >> ";  
        }
        echo $v[name];
        $multiple = true;
    }
}

In diesem Beispiel wird der Pfad von der Wurzel zu einem bestimmten Knoten abgefragt und ausgegeben

Noch ein Beispiel mit Einrückung:

PHP:
$branchNodes = $nestedSet->getBranch($id, true);
if($branchNodes != false) {
    foreach($branchNodes as $k => $v) {
        for($z = 1; $z <= $v[level]; $z++) {
            echo "&nbsp;";
        }
        echo $v[name].", ID: ". $v[id] ."<br>";
    }
}

Will man beispielsweise in einem Forum nur alle Topics ausgeben, genügt folgendes:

PHP:
$rootNodes = $nestedSet->getRootNodes($id, true);
if($rootNodes != false) {
    foreach($rootNodes as $k => $v) {
        echo $v[name].", ID: ". $v[id] ."<br>";
    }
}

Einrückung wird ja hier nicht benötigt, da Rootnodes ja immer level = 1 haben. Aber ich entdecke gerade ein kleines Problem: will man die Sortierung nach Datum vornehmen (oder Datum des letzten Eintrags), kann man das bisher noch nicht ..... In der nächsten Version liefere ich eine Option zur Modifikation des SQL Statements nach.....

Naja, und so kann man sich durch die Methoden arbeiten.
getDescendants() gibt alle Kinder eines Knotens aus, exklusive dem Knoten selber.
getBranch() macht das ganze inklusive des Knotens.
getSiblings() liefert alle Geschwister
getParent() gibt die direkte Mutter zurück, getParents() gibt alle Eltern zurück (naja, also Opa und Uropa .... ;) )

Und die Methode mit der man alle Voränger (also den Stammbaum) eines Knotens bekommt, die habe ich ja hier erfragt, aber noch immer nicht zusammenbekommen. Aber ich schaff das noch ....

So ... :) kleine Erklärung dieser Klasse .... Wie gesagt, ist die erste Version. Die Versionsnummer resultiert aus der CVS ID, keine Ahnung warum die so hoch ist. Aber ich folge jetzt einfach der ID :)

In einer der nächsten Versionen werde ich noch weitere Extras einbauen. Man kann dann einer get Methode ein BitFeld übergeben, dass einige Optionen markiert. Beispielsweise kann man dann angeben, ob man bei jedem Knoten zurück geliefert haben will, wie groß die Anzahl der Geschwister ist oder wieviele Nachkommen ein Knoten hat (wieder: Berechnung erfolgt direkt in der Datenbank durch den Query).

Ciao, Jörg
 
Zuletzt bearbeitet:
Wow! Verflucht, herzlichen Dank! Sehr gut erklärt und ausführlich! Das könnte man eigentlich glatt in Tutorials verschieben/kopieren, denke ich! Eigentlich bewerte ich keine Themen, aber in diesem Fall: 5 Sterne! :)
 
Zurück