Navigation aus Mysql-Tabelle
lollo
- programmiertechnik
Hallo,
ich bin gerade dran, aus einer mysql-Tabelle eine Navigation mit php dynamisch erstellen zu lassen. Doch leider fehlt mir die zündende Idee.
Meine Tabelle sieht so aus:
| ID | PARENT_ID | ORDER_ID | TITLE |
Es ist jeweils der Titel der aktuellen Seite bekannt und die Navigation soll bis dahin "ausgefaltet" werden und der Link zur aktuellen Seite soll dabei durch einen einfach Schriftzug in fetter Schrift ersetzt werden (id="current"). Doch wie stelle ich das am sinnvollsten an und bearbeite es mit php, sodass ich möglichst flexibel bleibe, wie ich die Navigation nachher auch wirklcih ausgebe.
Beispiel:
| ID | PARENT_ID | ORDER_ID | TITLE |
| 1 | 0 | 1 | startseite |
| 2 | 0 | 2 | ausgeklappt |
| 3 | 2 | 1 | unterseite |
| 4 | 2 | 2 | AKTUELLE SEITE! |
Wie lese ich das am besten aus, wenn ich "AKTUELLE SEITE!" gegeben habe.
Alles irgendwie in ein Array packen?
Gruß
Hallo,
ich bin gerade dran, aus einer mysql-Tabelle eine Navigation mit php dynamisch erstellen zu lassen. Doch leider fehlt mir die zündende Idee.
Google mal nach Nested Sets
Viele Grüße,
Horst Haselhun
Hallo,
vielen Dank für deine Antwort. Sehr interessant das Thema. Der erste Treffer bei Google hat mir schon einen guten Eiblick gegebne.
Nun habe ich aber doch paar Fragen:
Ich möchte die Statements ja nicht nur abtippen, sondern auch verstehen, leider steige ich bei der Verwendung von n.name (o.ä.) aus. Was hat es damit auf sich, ich habe leider nichts finden können, da mir auch der entsprechende Begriff fehlt um danach zu suchen.
Ist es bei Nested Sets überhaupt sinnvoll möglich, nachträglich bestimmte Elemente zu verschieben etc. oder muss ich dann immer den ganzen Baum neu berechnen? Wenn ich zum Beispiel 2 Nachfahren vertaushcen möchte, geht das ja noch einfach, aber wenn ich einen Nachfahre zu einem Bruder seines Vorfahren machen möchte?
Ich habe ja bei meiner Datenstruktur keine oberste Menge. oder ist meine oberste Menge dann einfach nicht existent und ich habe einfach für jede Kategorie gleich einen Nachfarhen in meiner Tabelle stehen?
Also z.B. Kategorie a mit Uunterpunkten bcd und Kategorie e mit Unterpunkten gh
Müssten die LFT und RGT-Werte dann wie folgt aussehen:
a 1 5
b 2 6
c 3 7
d 4 8
e 9 12
g 10 13
h 11 14
Gruß
Kleiner Witz am Rande ^^
echo $begrüßung;
- Ich möchte die Statements ja nicht nur abtippen, sondern auch verstehen, leider steige ich bei der Verwendung von n.name (o.ä.) aus. Was hat es damit auf sich, ich habe leider nichts finden können, da mir auch der entsprechende Begriff fehlt um danach zu suchen.
SELECT feld FROM tabelle;
Hier ist es eindeutig, dass sich feld auf tabelle bezieht
SELECT feld FROM tabelle1, tabelle2;
Worauf bezieht sich nun feld? Diese Frage zu klären ist vor allem dann wichtig, wenn feld in beiden Tabellen vorkommt.
SELECT tabelle1.feld, tabelle2.feld FROM tabelle1, tabelle2;
Nun ist deutlich, welches feld gemeint ist. Für tabelle1 und tabelle2 kann man auch Alias-Namen vergeben, wobei das AS optional ist.
SELECT t1.feld, t2.feld FROM tabelle1 AS t1, tabelle2 t2;
Das Beispiel verwendet nun die Tabelle tree zweimal, ein so genannter Self-Join
SELECT n.name, ... FROM tree AS n, tree AS p WHERE n.lft BETWEEN p.lft AND p.rgt ...
Die beiden Tabellen n und p (dass jedes Mal die selbe Tabelle dahintersteckt ist erstmal nebensächlich) ergeben ein kartesisches Produkt, das heißt, jeder Datensatz von n wird mit jedem Datensatz von p verknüpft. Danach greift sich das WHERE die Knoten heraus, die Kinder anderer Knoten sind.
- Ist es bei Nested Sets überhaupt sinnvoll möglich, nachträglich bestimmte Elemente zu verschieben etc. oder muss ich dann immer den ganzen Baum neu berechnen?
Zumindest der Teil muss neu berechnet werden, dessen left-Werte (und damit auch right-Werte) größer sind als die des zu änderenden Knotens. Diese Neuberechnerei ist ein Nachteil von Nested Sets. Wenn sich die Struktur ständig (und noch dazu zu großen Teilen) ändert, muss man schon genau überlegen, ob der Mehraufwand den Einsatz von NS rechtfertigt.
Wenn ich zum Beispiel 2 Nachfahren vertaushcen möchte, geht das ja noch einfach, aber wenn ich einen Nachfahre zu einem Bruder seines Vorfahren machen möchte?
Solange keine Kinder dranhängen ist das noch einfach mit Löschen und Einfügen zu machen.
- Ich habe ja bei meiner Datenstruktur keine oberste Menge. oder ist meine oberste Menge dann einfach nicht existent und ich habe einfach für jede Kategorie gleich einen Nachfarhen in meiner Tabelle stehen?
Ein Root-Element muss vorhanden sein. Wenn du mehrere Root-Elemente hast, dann hast du quasi mehrere vollständige und unabhängige Bäume in einer Tabelle. Das geht, braucht aber ein Kennzeichen, zu welchem Baum das Element gehört. Und jeder Baum hat seine eigene Left-Right-Zählung, sonst klappen die diversen Abfrage-Formeln (= SELECT-Statements) nicht.
Also z.B. Kategorie a mit Uunterpunkten bcd und Kategorie e mit Unterpunkten gh
Müssten die LFT und RGT-Werte dann wie folgt aussehen:
a 1 5
b 2 6
c 3 7
d 4 8
e 9 12
g 10 13
h 11 14
Das ist ungültig. Ein Element ohne Nachfahren zeichnet sich dadurch aus, dass right gleich left+1 ist. Solche Elemente hast du nicht in deinem Baum.
Nested Sets ist schon ein mächiges und interessantes Werkzeug. Es ist nur die Frage, ob du für deinen Fall mit - ich vermute einfach mal - einer Handvoll Menüeinträge dieses mächtige Werzeug benötigst, oder ob nicht auch ein einfacher Verweis auf das Elternelement reicht, mit anschließendem Aufbau des Baums in der abfragenden Umgebung.
echo "$verabschiedung $name";
Hallo,
leider sind es dich recht viele und unterschiedlich verschachtelte Einträge.
Deshalb finde ich NS schon ganz sinnvoll dafür.
Die Frage ist halt mit dem Verschieben mit Unterpunkten, wenn das nicht akzeptabel zu bewerkstelligen ist, kann ich mein Menü hinterher ja nur schwerlich umstellen. Die Performance ist dabei nebensächlich, da ich ja nicht menütlich mein Menü ändere.
Da ist mir wohl ein Lapsus unterlaufen, aber das mit dem Elternelement bringt mich etwas durcheinander, was ist denn dann bei mir mein Elternelement? Eine leere Zeile mit LFT 1 und RGT eben der letzte RGT-Wert?
Root
1 16
a e
2 9 10 15
b c d f g
3 4 5 6 7 8 11 12 13 14
Gruß
echo $begrüßung;
Die Frage ist halt mit dem Verschieben mit Unterpunkten, wenn das nicht akzeptabel zu bewerkstelligen ist, kann ich mein Menü hinterher ja nur schwerlich umstellen.
Es geht schon, ist aber mit etwas Überlegung verbunden.
Da ist mir wohl ein Lapsus unterlaufen, aber das mit dem Elternelement bringt mich etwas durcheinander, was ist denn dann bei mir mein Elternelement? Eine leere Zeile mit LFT 1 und RGT eben der letzte RGT-Wert?
Ja.
Root
1 16a e
2 9 10 15b c d f g
3 4 5 6 7 8 11 12 13 14
Wenn du Teilbäume umhängen willst, dann mach das doch mal mit ein paar Beispielen. Hänge einen Teilbaum nach "rechts" und mal nach "links". Beobachte, wie sich die L-R-Werte verändern und versuche eine Gesetzmäßigkeit daraus abzuleiten. Dann kannst du das in Code fassen.
echo "$verabschiedung $name";
Hallo,
vielen Dank für deine Antwort.
Das werde ich definitv mal probieren, damit etwas rumzuspielen.
Zurzeit habe ich das Problem, die Navigation in der Form abzufragen, in der ich sie gerne hätte.
Also es sollen immer die ersten "Unterpunkte" des Roots als Kategorieen angezeigt werden. Des weiteren soll an der Stelle, an der man sich gerade befindet, der Baum ausgeklappt werden und es sollen jeweils alle Unterpunkte dieses Kategorei angezeigt werden.
Gruß
echo $begrüßung;
Also es sollen immer die ersten "Unterpunkte" des Roots als Kategorieen angezeigt werden. Des weiteren soll an der Stelle, an der man sich gerade befindet, der Baum ausgeklappt werden und es sollen jeweils alle Unterpunkte dieses Kategorei angezeigt werden.
Rekursive Abfragen an die DB sind nicht günstig, weil dabei jedesmal eine Anfrage hin- und ein Ergebnis zurückgesendet wird. So ein Prozess ist verhältnismäßig aufwendig, selbst wenn du mit einem Prepared Statement das SQL-Parsen auf ein Minimum reduzieren kannst. Das müsstest du aber tun, wenn du nicht alles auf einmal abfragen und die Verwandschaftsbeziehungen im abfragenden Programmteil auswerten willst.
Angenommen, du hast dich entschieden, das Menü komplett abzufragen.
| ID | PARENT_ID | ORDER_ID | TITLE |
| 1 | 0 | 1 | startseite |
| 2 | 0 | 2 | ausgeklappt |
| 3 | 2 | 1 | unterseite |
| 4 | 2 | 2 | AKTUELLE SEITE! |
Du müsstest nun zunächst ermitteln, dass "AKTUELLE SEITE!" Parent_ID 2 hat. Danach suchst du alle Elemente mit Parent_ID=2 zusammen, und sortierst sie nach Order_ID. Damit hast du die ausgeklappte Liste. Nun suchst du dir das Element das die Parent_ID als ID hat und ermittelst dessen Parent_ID. Das machst du solange bis Parent_ID 0 ist. Damit hast du den Pfad.
echo "$verabschiedung $name";
Hallo,
eigentlihc meinte ich mit den Nested Sets ^^
Gruß
echo $begrüßung;
eigentlihc meinte ich mit den Nested Sets ^^
Dann fällt mir im Moment nur eine Lösung mit mehreren Abfragen ein, auch wenn man die vielleicht mit UNION zusammenfassen kann.
Alle Hauptmenüpunkte bekommst du mit einer Abfrage wie in http://www.klempert.de/nested_sets/artikel/#kap3 plus angehängtem HAVING level=1.
Für alle Geschwister eines bestimmten Zweiges brauchst zunächst du das Elternelement. Das ist das, dessen left kleiner und dessen right größer ist als das eines der Kinder. Außerdem darf es nur ein Level entfernt sein. Dessen Kinder sind alle Elemente, die zwischen left und right liegen.
Die Kette bis zum Ursprung ergibt sich ebenfalls aus den Elementen, deren left kleiner und dessen right größer ist als das des aktuellen Elements, aber ohne die Level-Einschränkung.
echo "$verabschiedung $name";
Hallo,
durch viel probieren bin ich (glaube ich) zu einer Lösung gekommen:
---
SELECT n.title, COUNT(*) AS ebene
FROM navi AS n, navi AS p
WHERE n.left
BETWEEN p.left
AND p.right
GROUP BY n.left
HAVING ebene = 1 OR (left
BETWEEN $left-wert-der-aktuellen-seite$ AND $right-wert-der-aktuellen-seite$ AND ebene <= $ebene-der-aktuellen-seite$+1)
ORDER BY n.left
---
$left-wert-der-aktuellen-seite$
$right-wert-der-aktuellen-seite$
$ebene-der-aktuellen-seite$
Sind bekannt, da man zuvor schon die aufgerufene Seite aus der Datenbank abgefragt hat.
Begehe ich mit diesem Statement einen großen Fehler? Wie könnte ich das Statement eventuell verbessern? Wo sollten jetzt am besten die Index liegen?
Gruß
P.S.: Leider werden nur die vorhergehenden Schwester-Elemente berücksichtigt. Doch wie ich das jetzt noch ändern könnte weiß ich leider auch nicht ...
echo $begrüßung;
Meine Tabelle sieht so aus:
| ID | PARENT_ID | ORDER_ID | TITLE |
Es ist jeweils der Titel der aktuellen Seite bekannt und die Navigation soll bis dahin "ausgefaltet" werden [...]
Wenn du stets die sämtliche Punkte der Navigation brauchst, dann brauchst du keine Nested Sets, weil du immer alle Daten auslesen musst. Brauchst du immer nur einen Teilstrang, dann bieten die Nested Sets (erkauft mit erhöhtem Änderungsaufwand) viele Möglichkeiten der teilweisen Abfrage an.
Ein Problem bleibt aber immer das gleiche: Wenn du in der Ergebnismenge mehr als einen gradlinigen Weg hast (gradlinig ist z.B. eine "Sie sind hier"-Anzeige Hauptmenüpunkt->Untermenüpunkt->Thema), ist die beste Darstellungsform eine Baumstuktur. Der Vorteil der Baumstruktur ist, dass man rekursiv alle Zweige erreichen kann. Datenbankabfragen liegen jedoch immer als ein "Stapel" vor. Eine Baumstruktur muss erst unter Berücksichtigung der Verweise aufgebaut werden. Wenn deine Menütabelle keine weiteren Beziehungen zu anderen Tabellen hat, wäre es sicher sinnvoller und weniger aufwendig, diese Baumstruktur gleich als Quelltext in einer Datei anzulegen, beispielsweise als verschachtelte Arrays, wenn PHP zum Einsatz kommt.
echo "$verabschiedung $name";
Hallo,
vielen Dank nochmal für die Hilfe.
Also das ganze soll ja für so ne Art "CMS" sein (letztendlich ist es aber eine Artikel-Sammlung mit paar vielen Artikeln, die halt unterschiedlich verschachtelt sind).
Jetzt bin ich mir noch nicht ganz einig darüber, wie ich das ganze am besten Organisiere.
Bsp.:
left | right | title | escaped_title | content |
---|---|---|---|---|
2 | 3 | Der Zeitgeist | " | |
4 | 13 | Menschen | " | |
5 | 6 | Solche | " | |
7 | 12 | ... und Solche | " | |
8 | 9 | mit viel Geld | " | |
10 | 11 | an der Armutsgrenze | " |
(ich denke jeder weiß was in escaped\_title
und content
zu erwarten ist)
So daraus soll nun erstens eine sinnvolle Navigation entstehen und die Seite soll ausgelesen werden mit Hilfe des escaped\_title
.
Nun die Fragen:
Würdet ihr, die übergebenen Adressen zum Artikel (aneinanderreihung der jeweiligen escaped\_title
) jedes mal prüfen oder einfach den letzten escaped\_title
aus der Tabelle abrufen?
z.B. http://foo.bar.org/index.php?ebene1=Menschen&ebene2=Solche
Jezt würde es ja langen, wenn ich einfach ebene2 aus der Datenbank hole und gut ist, oder würdet ihr jedesmal überprüfen, ob der "Pfad" bis in dem Fall ebene2 existiert?
Wie würdet ihr die Navigation "erstellen"?
Also sie sollte so aussehen, dass sie die Kategorien (im Beispiel: "Der Zeitgeist" und "Menschen" immer anzeigt und an der Stelle an der man sich gerade befindet den Baum so ausklappt, dass die Schwesterelemente des aktuellen Artikels und gegebenenfalls dessen Kindelemente angezeigt werden, sprich im Beispiel "... und Solche":
Der Zeitgeist
Menschen
Solche
... und Solche
mit viel Geld
an der Armutsgrenze
Würdet ihr dafür den kompletten Baum mit Ebene abfragen und schonmal beim Mysql-Statement alle Ebenen "abschneiden", die eh nicht angezeigt würden, in dem Beispiel eben alles > Ebene 4 (dafür würde aber das erste Statement um einiges komplexer, da man, um die aktuelle Ebene auszulesen, ja auch wieder einen SelfJoin machen müsste).
Oder würdet ihr einfach den kompletten Baum auslesen
SELECT n.title, COUNT(*) AS ebene
FROM navi AS n, navi AS p
WHERE n.left
BETWEEN p.left
AND p.right
GROUP BY n.left
ORDER BY n.left
Und diesen dann mit (in dem Fall) php weiter druchgehen? Wenn ja wie würde man das am Geschicktesten anstellen?
Gruß
Hallo,
also ich habe nun zu meiner Tabelle noch eine Spalte level
hinzugefügt.
Nun wird zuerst der aktuelle Artikel abgerufen.
Dann dessen "Elternelement" aus der ersten Ebene (die ja immer angezeigt werden soll)
Und dann ermittle ich die Navigation wie folgt:
SELECT level, link_address, title
FROM '.S_CMS_TABLE.'
WHERE level = 0
OR (left
> '.$_PARENT['left'].' AND right
< '.$_PARENT['right'].' AND level <= '. $_PAGE['level'].')
OR (left
> '.$_PAGE['left'].' AND right
< '.$_PAGE['right'].' AND level <= '. $_PAGE['level'].' + 1)
ORDER BY left
Das funktioniert auch einwandfrei. Nun ist die Frage, wo ich die Indexes setzte (Unique, Prmary) und ob man an diesen Abfrage noch etwas verbessern/beschleunigen/vereinfachen kann?
Gruß