Tabellenverknüpfungen :/
-
Danke!
- zu jedem Eintrag die cat_id solange nach oben zurückverfolgen, bis du bei der gewünschten ID oder -1 angelangt bist. Die mit -1 sind dann uninteressant
Ne, ich will eigentlich genau in die andere Richtung!
Von einer bestimmten Kategorie also alle cat_ids nach unten verfolgen, bis keine cat_of_ids mehr gefunden werden.Vielleicht hat auch jemand einen Vorschlag für ein besseres Datenbankdesign.
Es sollte folgende Kriterien erfüllen:
-> Beliebig viele Kategorien, mit beliebig vielen Unterkategorien
-> Beliebig viele Einträge, in jeder beliebigen Kategorie
-> Ein Eintrag kann in mehreren Kategorien erscheinen
-> Bei einer Suchfunktion sollten sich die Treffer ohne größeren Aufwand auf eine bestimmte Kategorie beschränken lassen.
-> Alle Untereinträge einer bestimmten Kategorie sollen auf einfache Weise zugänglich sein (daran scheitert es momentan...)
-
Hmm, beim obigen Post hatte ich wohl mein Hirn bisschen auf Standby gesetzt.
Du hattest natürlich recht, mit von unten anfangen, sonst verzweigt sich das ja und wird noch komplizierter oO.Naja, ich bin im Moment echt am überlegen, wie die Datenbank noch aussehen könnte. Unbegrenzt viele Kategorien sind eigentlich unwichter, aber ich wüsste auch nicht wie ich das abändern könnte, damit sich dafür mein momentanes Problem löst.
-
Wenn man die Performance mal aussen vor lässt, dann fallen mir 2 Möglichkeiten ein, wie man das Problem lösen könnte:
1. Du speicherst in einer Spalte alle Eltern-Kategorien einer Kategorie, z.b. in der Form:
cat_id | cat_name | cat_parent | cat_refs 1 | Kat01 | Null | 2 | UKat01 | 1 | |1| 3 | UKat02 | 2 | |1|2| 4 | UKat03 | 1 | |1|
So kannst Du alle "Unter"-Kategorien mit einem Stmt holen:
$cats = array(); $sql = "select cat_id from categories where cat_id = " . $cat_id . " or cat_refs like '%|" . $cat_id . "|%'"; // ... while ($row = ...) { array_push($cats, $row['cat_id']); } $sql = "select * from entries where cat_id in (" . implode(",", $cats) . ")"; // ...
2. Du schreibst Dir eine Funktion, die rekursiv alle Unterkategorien zurückliefert:
function getSCats($cat_id, &$cats) { array_push($cats, $cat_id); $sql = "select * from categories where cat_parent = " . $cat_id; // ... while ($row = ...) { getSCats($row['cat_id'], $cats); } } $cats = array(); getSCats($mycat_id, $cats); $sql = "select * from entries where cat_id in (" . implode(",", $cats) . ")"; // ...
-
Ja, das sind zwar zwei gute Lösungen, aber trotzdem habe ich an beiden etwas auszusetzen.
Der rekursive Ansatz scheint mir einfach total rechenintensiv zu sein, wobei ich selbst eigentlich gar nicht weiß, wie hoch der Rechenaufwand wirklich ist.
Das andere hat da imho einen großen Vorsprung, ist dafür aber nicht annährend so dynamisch. Zwar muss man den Baum nur einmal aufbauen, aber wenn man einen Eintrag z.B. verschiebt, ist es ein mehraufwand.
Hmm, was haltet ihr für besser? Gebt mir bitte noch paar Ratschläge, ich mache zum ersten mal etwas bisschen größeres mit Sql und wills nicht verbocken.
-
So ihr noobs, ich wusste doch, dass ihr nix könnt!
Ich bin zwar fertig mit dem Ding, aber heute erfahre ich von einer viel besseren Möglichkeit, von der ihr natürlich alle nix gewusst habt.Das Nested Sets-Modell ist nämlich genau das, was ich wollte!
Jetzt heißt es nochmal von vorne anfangen.
-
Hmm, bräuchte nochmal Hilfe.
http://www.klempert.de/nested_sets/artikel/
Das ist eine kleine Einführung, in das Nested-Sets-Modell, aber ich kann diesen Query nicht nachvollziehen:SELECT n.name, COUNT(*)-1 AS level FROM tree AS n, tree AS p WHERE n.lft BETWEEN p.lft AND p.rgt GROUP BY n.lft ORDER BY n.lft;
Der Query selektiert mir alles in meiner Tabelle, aber irgendwie muss das ja rekursiv ablaufen? Also irgendwie verstehe ich das überhaupt nicht....
Wäre um eine möglichst detailierte Erklärung sehr dankbar!
Und entschuldigt, dass ich oben bisschen rumgepöbelt habe, aber das war halt ein herber Schock erstmal, versteht ihr hoffentlich.
-
Mich würde dabei vielmehr interessieren, wie ich bestimmte Teilbäume aus dem Ergebnis entfernen kann.
Ich habe bisher immer nur gesehen, wie man den gesamten Baum bzw. Teilbäume holt, aber wie ich den gesamten Baum ohne bestimmte Teilbäume holen kann habe ich nirgends finden können.
Bei der rekursiven Variante kann ich einfach ein Array mit IDs übergeben, die "offen" sein sollen und sobald ich eine ID abfrage, die nicht im Array enthalten ist, dann breche ich die Rekursion ab, was wunderbar funktioniert. Leider funzt das bei NestedSets aber nicht.
Ich kann zwar einen Datensatz ausklammern, aber nicht unbedingt einen gesamten Unterbaum.
Bsp: Folgender Baum sei gegeben:
A |- B | |- C | | `- D | `- E `- F |- G `- H
Wie müsste eine Abfrage aussehen, die mir einen Baum holt, der so aussieht?
A |- B (Knoten geschlossen) `- F |- G `- H
Wenn ich z.B. ein Array mit offenen Knoten habe, das so aussieht?
$offen = array('A', 'C', 'F');
Bei der rekursiven Variante wird schon bei 'B' der Teilbaum verlassen, bei NestedSets habe ich es bisher nur hinbekommen, dass zwar 'B' nicht angezeigt wird, aber der Unterbaum von 'C' dann doch wieder, also ungefähr so:
A | |- C | | `- D `- F |- G `- H
was natürlich völlig sinnlos ist.
Hat da jmd. eine Idee?
Natürlich hätte ich gerne eine Lösung, die mir bereits bei der Abfrage den korrekten Baum liefert. Nachträglich im Script kann man das ja mit Hilfe der Tiefe 'filtern', was aber wieder ziemlich unperformant ist.
-
Also, ich habe mich jetzt nochmal so gut es ging damit auseinandergesetzt und verstehe das Query jetzt annähernd!
Aber das GROUP BY n.lft ergibt für mich keinen Sinn... Was soll es zusammenfassen? Der lft-Wert ist doch eindeutig...mantiz:
Ich hab dein Problem jetzt ganz einfach gelöst bekommen:
Du musst einfach nur darauf achten, dass dein lft nicht zwischen den lft- und rgt-Wert des Astes liegt, den du ausblenden möchtest.
Bei mir hat das mit obigen Query geklappt, indem ich das angehängt habe:AND n.lft NOT BETWEEN 2 AND 19
2 und 19 sind dabei rgt und lft des Astes, den ich ausblenden möchte.
-
2 und 19 sind die rgt- und lft-Werte +1!
-
Was mir jetzt auch noch Kopfzerbrechen bereitet, ist, wie man alle Einträge eines "Levels (egal ob Ast oder Blatt) ausgeben lassen kann.
Also an deinem Beispiel:
A |- B | |- C | | `- D | `- E `- F |- G `- H
Wie kann ich nur die Kinder von A, also B und F, ausgeben lassen?
Ich weiß, dass zwischen B und F folgender Zusammenhang besteht:
B.rgt = F.lft +1
Und so lässt sich das für alle weiteren Kinder von A beliebig fortsetzen...
vorgänger.rgt = nachfolger.lft +1
Aber ich kann mir kein Query basteln, das entsprechend Arbeitet...
Rekursiv würde ich es wohl schaffen, aber es geht ja genau darum, das zu verhindern...
-
hm, gute Frage. Hab' gerade mal ein wenig gegrübelt, aber bin nicht wirklich dahintergestiegen.
Evtl. fällt mir morgen etwas ein.
Bzgl. der Teilbäume: Ich will ja nicht bezielt einen Baum ausblenden, also erst den gesamten Baum holen und dann einen Ast ausblenden, sondern eher erst alles (bis auf Wurzel) ausblenden und dann gezielt einblenden. Evtl. kommt's einfach nur auf den Blickwinkel drauf an, mal sehen.
Falls mir noch was einfällt, dann geb' ich auf jeden Fall Bescheid. Ich würd' mir aber nicht zuviel Hoffnungen machen, so wirklich scheine ich es noch nicht geblickt zu haben.
-
hmpf, also eigentlich müsste Dein Problem so ungefähr zu lösen sein:
SELECT n.*, round((n.tr_rgt-n.tr_lft-1)/2,0) AS childs, count(*)+(n.tr_lft>1) AS level, ((min(p.tr_rgt)-n.tr_rgt-(n.tr_lft>1))/2) > 0 AS lower, (( (n.tr_lft-max(p.tr_lft)>1) )) AS upper FROM testbaum n, testbaum p WHERE n.tr_lft BETWEEN p.tr_lft AND p.tr_rgt AND (p.tr_root = n.tr_root) AND (p.tr_id != n.tr_id OR n.tr_lft = 1) AND n.tr_lft BETWEEN 1 AND 14 AND (level = 2) GROUP BY n.tr_root, n.tr_id ORDER BY n.tr_root, n.tr_lft
allerdings bekomme ich immer die Fehlermeldung:
#1054 - Unknown column 'level' in 'where clause'
Aber irgendwie muss ich doch auf 'level' filtern können ... *grübel
PS: Bei mir sind 1 und 14 die lft- bzw. rgt-Werte von der Wurzel, level = 2 wären also dann die direkten Kinder.
// EDIT:
Argh, manchmal kann es so einfach sein ... *schäm*SELECT n.*, round((n.tr_rgt-n.tr_lft-1)/2,0) AS childs, count(*)+(n.tr_lft>1) AS level, ((min(p.tr_rgt)-n.tr_rgt-(n.tr_lft>1))/2) > 0 AS lower, (( (n.tr_lft-max(p.tr_lft)>1) )) AS upper FROM testbaum n, testbaum p WHERE n.tr_lft BETWEEN p.tr_lft AND p.tr_rgt AND (p.tr_root = n.tr_root) AND (p.tr_id != n.tr_id OR n.tr_lft = 1) AND n.tr_lft BETWEEN 1 AND 14 GROUP BY n.tr_root, n.tr_id HAVING level = 2 ORDER BY n.tr_root, n.tr_lft
PS: Having bei Group by ist Dein Freund.
-
Hey, ich denke ich hab' mein Problem auch gelöst.
SELECT n.*, COUNT(*) - 1 AS level FROM testbaum AS n, testbaum AS p WHERE n.tr_lft BETWEEN p.tr_lft AND p.tr_rgt GROUP BY n.tr_root, n.tr_lft HAVING (n.tr_lft BETWEEN 1 AND 14 AND level = 0) OR (n.tr_lft BETWEEN 2 AND 13 AND level = 1) OR (n.tr_lft BETWEEN 3 AND 6 AND level = 2) /* OR (n.tr_lft BETWEEN 9 AND 12 AND level = 2) */ ORDER BY n.tr_root, n.tr_lft
Wenn das letzte OR in der HAVING-Klausel auskommentiert bleibt, dann ist das zweite Kind (lft: 8 und rgt: 13) auf Ebene 1 geschlossen, wenn ich den Kommentar mit verwende, dann ist der Baum komplett geöffnet. Für die Wurzel benutzt man die imaginären Werte lft: 0 und rgt: 15, wodurch dann 1 und 14 entstehen, da lft immer lft+1 und rgt = rgt-1 zum filtern ist.
Jetzt muss ich mir nur noch überlegen, wie ich erkenne, wenn (wie in meinem Beispiel) level 1 nicht geöffnet ist, für level 2 aber etwas geöffnet ist, was ja dann nicht als geöffnet dargestellt werden darf.
-
Die HAVING-Klausel sieht wirklich sehr vielversprechend aus!
Was für Werte stehen bei dir in tr_root, ich habe diese Spalte nicht.Das Query von der Tutorial-Seite lies sich mit der HAVING-Klausel auch leicht erweitern:
SELECT o.*, COUNT(p.id)-1 AS level FROM test AS n, test AS p, test AS o WHERE o.lft BETWEEN p.lft AND p.rgt AND o.lft BETWEEN n.lft AND n.rgt AND n.id = 1 GROUP BY o.lft HAVING level = 1 ORDER BY o.lft;
Ergebnis sind auch wieder alle Kinder der Wurzel.
Allerdings braucht man jetzt hier, und so weit ich das erkennen kann, bei deiner Variante auch, immer zwei Parameter, um die Ergebnisse darstellen zu können. Das Level und die ID des entsprechenden Astes bei dieser Variante, das Level und die lft/rgt-Werte des entsprechenden Astes bei deiner Variante.
-
MySql-Doofi schrieb:
Ergebnis sind auch wieder alle Kinder der Wurzel.
Allerdings braucht man jetzt hier, und so weit ich das erkennen kann, bei deiner Variante auch, immer zwei Parameter, um die Ergebnisse darstellen zu können. Das Level und die ID des entsprechenden Astes bei dieser Variante, das Level und die lft/rgt-Werte des entsprechenden Astes bei deiner Variante.Ja, das sehe ich auch so, ich denke da kommt man auch nicht drumherum.
MySql-Doofi schrieb:
Die HAVING-Klausel sieht wirklich sehr vielversprechend aus!
Was für Werte stehen bei dir in tr_root, ich habe diese Spalte nicht.Ich hab' bei meiner Beispiel-Tabelle die Möglichkeit mehrere Bäume in dieser Tabelle zu speichern, also mehrere Wurzeln. In tr_root steht also immer die tr_id der Wurzel. Wenn es sich um die Wurzel selbst handelt, dann ist tr_id = tr_root. Habe ich bei irgendeiner Seite so gesehen und gefiel mir.
-
oO
Du machst mir das zu kompilziert.
Ich glaube ein mühevolles anpassen deines Query kann ihc mir sparen, läuft ja eh auf das selbe raus.Nochmal zu den Leveln, das Level des Kindes eines Astes ist um 1 größer als das Level des Elternastes.
Wenn man jetzt die ID des Elternastes hat und somit das Level ermitteln kann, möchte man doch meinen, dass das ausreicht, um alle Kinder darzustellen. Aber ich krieg es auch nicht hin...
-
Jetzt bin ich verwirrt.
Also, meiner Meinung nach brauchst Du die lft-/rgt-Werte und das level, um die Kinder darzustellen, was imho auch logisch ist. Nehmen wir mal folgenden Baum:
A |- B | |- C | `- D `- E |- F `- G
Dazu gehört folgende Tabelle:
+-------+---------+---------+--------+--------+-------+ | tr_id | tr_root | tr_name | tr_lft | tr_rgt | level | +-------+---------+---------+--------+--------+-------+ | 1 | 1 | A | 1 | 14 | 0 | | 2 | 1 | B | 2 | 7 | 1 | | 3 | 1 | C | 3 | 4 | 2 | | 4 | 1 | D | 5 | 6 | 2 | | 5 | 1 | E | 8 | 13 | 1 | | 6 | 1 | F | 9 | 10 | 2 | | 7 | 1 | G | 11 | 12 | 2 | +-------+---------+---------+--------+--------+-------+
Wenn ich jetzt alle Kinder von 'B' haben möchte, dann muss ich doch alle Datensätze holen, die level 2 = getLevel('B')+1 und tr_lft > 2 und tr_rgt < 7 haben. Wenn ich die Bedingung der lft-/rgt-Werte weglasse, dann bekomme ich ja zusätzlich 'F' und 'G' als Kinder ausgegeben, was ja falsch wäre.
Hab' Dein Statement mit der ID (ohne lft/rgt) nicht überprüft, daher weiß ich nicht genau, ob das so läuft, daher hier nur die Erklärung, warum das mit den lft/rgt-Werte laufen sollte.
Die lft/rgt-Werte in einem Teilbaum TB eines Knotens K liegen immer zwischen den lft/rgt-Werten von K, deswegen ist dies als Filter-Bedingung imho unerlässlich.
-
Nochmal am Beispiel, alle Kinder von A anzeigen zu lassen:
+-------+---------+---------+--------+--------+-------+ | tr_id | tr_root | tr_name | tr_lft | tr_rgt | level | +-------+---------+---------+--------+--------+-------+ | 1 | 1 | A | [b]1[/b] | [b]14[/b] | 0 | | 2 | 1 | B | [b]2[/b] | [b]7[/b] | 1 | | 3 | 1 | C | 3 | 4 | 2 | | 4 | 1 | D | 5 | 6 | 2 | | 5 | 1 | E | [b]8[/b] | [b]13[/b] | 1 | | 6 | 1 | F | 9 | 10 | 2 | | 7 | 1 | G | 11 | 12 | 2 | +-------+---------+---------+--------+--------+-------+
Gegeben ist nur der lft-Wert von A!
1 -> 2 -> 7 -> 8 -> 13 -> 14
Alle Äste verhalten sich da ähnlich wie in einem Parent-Baum.
Für alle Äste, außer der Wurzel gilt immer:
row1.tr_rgt = row2.tr_lft+1Warum kann man das nicht nutzen? Ich probiere immer noch damit rum...
-
Um das zu machen müsstest Du ein Statement bauen, welches so ähnlich aussieht:
select * from `tree` where `tr_lft` = 2 or (`tr_lft` > 2 and `tr_lft` = `prev_row`.`tr_rgt` + 1) order by `tr_lft` asc
wobei Du hier natürlich einen Ausdruck für `prev_row` bräuchtest, den es afaik nicht gibt.
Mag sein, dass ich mich irre, aber ich glaube, dass man auf Felder des vorherigen Datensatzes in einer Liste nicht zugreifen kann.
Evtl. würde eine Möglichkeit bestehen, wenn Du die gleiche Liste, also in diesem Beispiel B und C versetzt aneinander joinen würdest, Du also eine Liste der Form
+---------+--------+--------+----------+---------+---------+ | tr_name | tr_lft | tr_rgt | tr_name2 | tr_lft2 | tr_rgt2 | +---------+--------+--------+----------+---------+---------+ | B | 2 | 7 | A | 1 | 14 | | E | 8 | 13 | B | 2 | 7 | +---------+--------+--------+----------+---------+---------+
Wenn Du einen Join in der Form hinbekommst, dann kannst Du ja einfach ein
where tr_lft = tr_lft2 + 1 or tr_lft = tr_rgt2 + 1
dranhängen, dann müsste das laufen.
Nur, wenn Du soweit bist, dass Du genau diese Liste dranjoinen kannst, dann brauchst Du eigentlich nicht mehr weitermachen, weil Du ja dann schon die Liste gefunden hast, die Dich interessiert.
Also, ich denke, das klappt so nicht, wie Du Dir das vorstellst.
Oder hast Du evtl. in der Zwischenzeit eine Lösung gefunden?
-
Ne, hab die Idee jetzt verworfen, und wie du weiter oben schon vorgeschlagen hast, eine zusätzliche Spalte level eingefügt. Funktioniert prima und ich bin soweit auch zufrieden.
mantiz, vielen vielen Dank für deine Hilfe, hast mir wirklich sehr gut geholfen!