luckger: NestedSets

Hallo zusammen,

ich versuche mich gerade an Nested Sets und versuche Folgendes zu bewerkstelligen.

In der Datenbank liegt eine Tabelle mit der gesamten Navigationsstruktur. Die Datenbankstruktur mit den Daten gibts hier (http://www.weingut-cuntz.de/db_struktur.jpg)

Ich möchte nun folgendes machen:
Unter dem Punkt Inhalte stehen zwei Unterpunkte: Seiteninhalte und Aktuelles. Darunter stehen wiederum zwei Unterpunkte, bei denen nur im Feld tasks etwas eingetragen ist.

Ich möchte nun nur die zwei tasks Unterpunkte ausgeben, wenn der User sich auf der übergeordneten Seite befindet.

Folgendes wäre mein Ansatz:

  
    SELECT  
     t1.*,  
     COUNT(*) AS ebene  
    FROM  
     admin_v3 AS t1,  
     admin_v3 AS t2  
    WHERE  
     t1.task != '' AND  
     t1.rgt BETWEEN t1.lft AND t2.rgt  
  
    GROUP BY  
     t2.lft  
    HAVING  
     t1.lft>" .$path[$ebene]['lft'] ." AND  
     t1.rgt<" .$path[$ebene]['rgt'] ." AND  
     ebene = $ebene+1  
    ORDER BY  
     t1.lft ASC";  

--------------
Zur Erklärung:
--------------
$path[$ebene]['lft'] und $path[$ebene]['rgt'] sind die bezeichnen selbige Elemente der gerade aufgerufenen Seite, wären also im Beispiel Seiteninhalte  15 und 22.

$ebene ist die aktuelle Ebene der gerade aufgerufenen Seite. In diesem Fall müsste das, wenn ich mich nicht irre, 2 sein.

------------
Das Problem:
------------
Das Problem an der ganzen Geschichte ist nur, dass wenn ich den Code so verwende, nur ein Unterpunkt ausgelesen wird.

Letztendlich sollen dann also nur die zwei Unterpunkte vfdffv und Task ausgegeben werden - und das auf keiner übergeordneten Seite.

Hat jemand eine Ahnung wo mein Fehler liegt? Ich komm einfach nicht weiter...

Vielen Dank schonmal im voraus und noch einen schönen Feiertag bzw. evtl. Brückentag,

luckger

  1. weiß denn niemand Rat?

    luckger

  2. echo $begrüßung;

    FROM
         admin_v3 AS t1,
         admin_v3 AS t2

    t1 und t2 sind recht aussagelose Bezeichner. Sie sagten mehr über ihre Bedeutung aus, gäbest du ihnen wie im Wikipedia-Beispiel Tiefensuche die Bezeichnungen node und parent. t1 ist also dein Knoten, den du haben möchtest, der Join mit t2 dient nur zum Ermitteln der Elternknoten, die zum Pfad bis zum aktuellen t1-Knoten gehören.

    Ich möchte nun nur die zwei tasks Unterpunkte ausgeben, wenn der User sich auf der übergeordneten Seite befindet.

    WHERE
         t1.task != ''

    Diese WHERE-Bedingung beschränkt die Ergebnismenge schon mal auf die Knoten, denen ein Task zugeordnet ist.

    t1.rgt BETWEEN t1.lft AND t2.rgt

    Unter anderem hier scheint mir der Fehler zu liegen. Erwähntes Wikipedia-Beispiel eignet sich ja schon mal zur Suche nach Knoten inklusive der Ermittlung ihrer Ebene. Gesucht werden alle Elternknoten zum aktuellen Knoten. Gemäß der Betrachtung als verschachtelte Mengen sind das diejenigen, die den aktuellen Knoten umschließen. Also sollte das t1.lft ein t2.lft sein. Da ein BETWEEN die angegebenen Grenzwerte mit einschließt, kommt der Knoten also ebenfalls - und zwar mit sich selbst verknüpft - in der Ergebnismenge vor.

    Wenn ein Knoten n Eltern hat, hast du nun (n+1)-mal den Knoten in der Ergebnismenge stehen. Weil wir auch noch zwecks Ermittlung dieses Wertes n+1, der ja die Ebene des Knotens darstellt, eine Gruppierung brauchen, muss über ein eindeutiges Merkmal des Knotens gruppiert werden.

    GROUP BY
         t2.lft

    t2 ist der Elternknoten. Davon hat ein Knoten mehrere unterschiedliche. Das ist also kein passendes Gruppierungsmerkmal. lft, rgt oder id eines Knotens (= t1) sind eindeutige Merkmale[1].

    GROUP BY t1.id

    wäre die richtige Gruppierung.

    SELECT
         t1.*,
         COUNT(*) AS ebene

    "ebene" entspricht dem Wert n+1 meiner obigen Erläuterung.

    t1.* geht nur unter MySQL. Mit anderen DBMS kann bei einer Gruppierung nur die gruppierenden Felder und Aggregatfunktionen verwenden, da das Ergebnis der anderen Felder im Allgemeinen nicht eindeutig ist. In unserem Fall ist es das jedoch, und hier hat MySQLs Eigenart einen Vorteil, weil wir ansonsten nur die id des Knotens als Ergebnis hätten und zum Ermitteln der restlichen Knotendaten ein weiteres Select benötigten, z.B. in Form einer Supquery[2].

    HAVING
         t1.lft>" .$path[$ebene]['lft'] ." AND
         t1.rgt<" .$path[$ebene]['rgt'] ." AND
         ebene = $ebene+1

    Die ersten beiden Bedingungen täten ohne MySQLs Eigenart auch nicht funktionieren, da ein HAVING nur Werte aus der SELECT-Klausel betrachten kann. Es ist aber nicht notwendig, sie in der HAVING-Klausel zu notieren. Diese Einschränkung kann gleich bei der ersten Einschränkung der Ergebnismenge notiert werden, also in der WHERE-Klausel. Einzig die Ebeneneinschränkung muss im HAVING verbleiben, denn dieser Wert steht ja erst nach der Gruppierung zur Verfügung, und zu dem Zeitpunkt ist WHERE schon Geschichte.

    [1] Solange man nur einen Baum in der Tabelle verwaltet. Hat man mehrere Bäume quasi parallel in einer Tabelle, muss man zu lft oder rgt noch den Baum-Identifizierer hinzunehmen, oder aber id, das über alle Datensätze eindeutig sein muss.

    [2] Nein, Supquery ist kein Schreibfehler, aber wohl auch kein allgemeingültiger Begriff. Gemeint ist das Gegenteil eines Subquerys, also ein Query außen drumrum. Oder andersrum betrachtet: Die eben besprochene Query, die nur eine ID liefert, muss als Subquery einer Abfrage notiert werden, die alle (gewünschten) Knotendaten zu einer/der gegebenen ID ermittelt.

    echo "$verabschiedung $name";

    1. Hi $name

      vielen Dank für deine Hilfe - vor allem für die Erklärung dazu. Das Ganze funktioniert jetzt echt klasse.

      Für alle die das gleiche Problem haben hier die finale sql-Abfrage:

        
       $sql = "  
          SELECT  
           node.*,  
           COUNT(*) AS ebene  
          FROM  
           admin_v3 AS node,  
           admin_v3 AS parent  
          WHERE  
           node.task != '' AND  
           node.rgt BETWEEN parent.lft AND parent.rgt AND  
           node.lft BETWEEN " .$path[$ebene]['lft'] ." AND " .$path[$ebene]['rgt'] ."  
          GROUP BY  
           node.id  
          HAVING  
           ebene=$ebene+2  
          ORDER BY  
           node.lft ASC"; 
      

      Vielen Dank nochmal und noch einen schönen Sonntag,

      luckger