Andrea H.: Baumstruktur mit rekursiver Query auslesen

Hallo

Ich habe nachfolgende hierarchische Datenstruktur in einer MSSQL Datenbank definiert - grundsätzlich besitzt jede "node" eine "parentnode". Weil es aber möglich ist, dass irgendwo im Baum eine Node auf eine Node an einem anderen Ort im Baum verweist, habe ich zusätzlich das Feld "referenceid" eingeführt. Wenn dieses nicht NULL ist bedeutet das, dass diese node auf eine andere Node zeigt (eine Art Querverweis innerhalb der Baumstruktur) - diese Node ist dann eigentlich nur eine Art "Hülle" und bei einer Abfrage soll der Inhalt der referenzierten Node angezeigt werden.

id | referenceid | parentid | description | node | refnode 0 | NULL | NULL | root node | / | NULL 1 | NULL | 0 | node | /1/ | NULL 2 | NULL | 1 | node | /1/1/ | NULL 3 | NULL | 0 | node | /2/ | NULL 4 | 8 | 3 | node* | /2/1/ | /4/ 5 | NULL | 0 | node | /3/ | NULL 8 | 9 | 0 | node* | /4/ | /3/1/ 9 | NULL | 5 | node | /3/1/ | NULL

Die Felder "node" und "refnode" sind vom Daten-Typ hierarchyid.

Entsprechend sollte die Abfrage dann folgenden Baum auslesen:

0           root node
+-1         node
  +-2       node
+-3         node
  +-8(4)    node* 4 die ein verweis auf 8 besitzt (die wiederum eine linknode ist)
    +-9(8)  node *8 die ein verweis auf 9 besitzt
+-4         node
+-5         node
  +-9       node
+-8         node

Für die Abfrage habe ich mir folgende Query geschrieben:

DECLARE @node as hierarchyid;
 
Select @node = node FROM nodes WHERE node.ToString() = '/'
 
;WITH cte AS (
        SELECT  
                id,
                referenceid,
                parentid, 
                description,
                node,
                refnode
        FROM nodes a 
        WHERE node.IsDescendantOf(@node) = 1
 
        UNION ALL
 
        SELECT 
                b.id,
                b.referenceid,
                b.parentid, 
                b.description,
                b.node,
                b.refnode
        FROM nodes b
        INNER JOIN cte ON b.node.GetAncestor(1) = cte.refnode OR b.node.IsDescendantOf(cte.node) = 1
) 
Select *, node.ToString() from cte

Leider liest sie mir den Baum nur teilweise aus (wenn am Verweis wieder ein Verweis hängt kommt der nicht mit). Kann mir jemand dabei helfen? Gibt es allenfalls bessere Möglichkeiten die Datenstruktur aufzusetzen?

Beste Grüsse Andrea H.

  1. Leider liest sie mir den Baum nur teilweise aus (wenn am Verweis wieder ein Verweis hängt kommt der nicht mit). Kann mir jemand dabei helfen? Gibt es allenfalls bessere Möglichkeiten die Datenstruktur aufzusetzen?

    Mach die Abfrage so, dass jeder Knoten bzw. Objekt seine Kindknoten kennt. Ziel also, aus der parent-Beziehung eine Child-Beziehung zu machen und im Ergebnis dessen hast Du eine Datenstruktur im Hauptspeicher, welche rekursiv durchlaufen werden kann.

    1. Tach!

      Mach die Abfrage so, dass jeder Knoten bzw. Objekt seine Kindknoten kennt. Ziel also, aus der parent-Beziehung eine Child-Beziehung zu machen und im Ergebnis dessen hast Du eine Datenstruktur im Hauptspeicher, welche rekursiv durchlaufen werden kann.

      Du meinst, in der Datenbank soll nicht nur vom Kind ein einzelner Verweis auf den Vorfahren vorhanden sein, sondern beim Vorfahren sollen eine unbekannte Anzahl Kindverweise gepflegt werden? Wie stellst du dir die dazu passende Struktur vor?

      dedlfix.

      1. Du meinst, in der Datenbank soll nicht nur vom Kind ein einzelner Verweis auf den Vorfahren vorhanden sein, sondern beim Vorfahren sollen eine unbekannte Anzahl Kindverweise gepflegt werden?

        Nein, das meine ich nicht, wie kommst Du den darauf!? Hast Du überhaupt verstanden was ich geschrieben habe?

        Wie stellst du dir die dazu passende Struktur vor?

        Jeder Knoten wird als Objekt betrachtet, in Perl wäre es es ein Hash nach dem EAV-Muster

        {
          Entity => { Attribute => Value},
          4711   => { childs => [] } # Arrayreferenz auf die Kindknoten
        }
        
        oder als Slice mit anonymen Hash-Referenzen
        [
          { Attribute => Value},
          { childs => [] } # Arrayreferenz auf die Kindknoten
        ]
        

        Für eine Rekursion reicht die Eigenschaft parent nicht, die parent-Beziehung muss vorher umgedreht werden, so dass in jedem Objekt ein Array mit den ID's der Kindknoten voliegt.

        Alle meine Foren arbeiten mit einer solchen Datenstruktur, Beispiel: http://handwerkzeugs.de/tforum.html

        Für die Tabelle in einer DB reichen übrigens 3 Felder um eine solche Datenstruktur abbilden zu können.

        1. Hi,

          Du meinst, in der Datenbank soll nicht nur vom Kind ein einzelner Verweis auf den Vorfahren vorhanden sein, sondern beim Vorfahren sollen eine unbekannte Anzahl Kindverweise gepflegt werden?

          Nein, das meine ich nicht, wie kommst Du den darauf!? Hast Du überhaupt verstanden was ich geschrieben habe?

          Jeder Knoten wird als Objekt betrachtet,

          Für eine Rekursion reicht die Eigenschaft parent nicht, die parent-Beziehung muss vorher umgedreht werden, so dass in jedem Objekt ein Array mit den ID's der Kindknoten voliegt.

          Du widersprichst Dir selbst - Du sagst, im Vorfahren sollen nicht alle Kindverweise gepflegt werden, sondern alle Kindverweise (als Array) gepflegt werden ...

          cu,
          Andreas a/k/a MudGuard

          1. Du widersprichst Dir selbst - Du sagst, im Vorfahren sollen nicht alle Kindverweise gepflegt werden, sondern alle Kindverweise (als Array) gepflegt werden ...

            Du verstehst das eben nicht: Den Unterschied zwischen Random Access und Persistenz. Das ist Grundwissen, was hier schon immer gefehlt hat, das wirst Du und die Anderen selbstherrlichen SELFer nie verstehen. Gottseidank hab ich keinen weiteren Code gepostet, schade um die Zeit.

            Aber ich finds trotzdem unverschämt, jeden meiner Beiträge mit -- zu bewerten. Zumal dahinter auch auch praktische Erfahrungen stecken, von denen so mancher hier nicht einmal träumen könnte.

            1. Du verstehst das eben nicht: Den Unterschied zwischen Random Access und Persistenz. Das ist Grundwissen, was hier schon immer gefehlt hat, das wirst Du und die Anderen selbstherrlichen SELFer nie verstehen. Gottseidank hab ich keinen weiteren Code gepostet, schade um die Zeit.

              Bullshit

        2. Tach!

          Du meinst, in der Datenbank soll nicht nur vom Kind ein einzelner Verweis auf den Vorfahren vorhanden sein, sondern beim Vorfahren sollen eine unbekannte Anzahl Kindverweise gepflegt werden?

          Nein, das meine ich nicht, wie kommst Du den darauf!? Hast Du überhaupt verstanden was ich geschrieben habe?

          Nun, anscheinend nicht - wie so oft.

          Mach die Abfrage so, dass jeder Knoten bzw. Objekt seine Kindknoten kennt.

          Das Missverstehen fängt wohl schon beim ersten Satz an. Wie gestaltet man eine Abfrage so, dass die Einträge in der Ergebnismenge bestimmte Dinge kennen? Sollen sie diese erst beim Abfragen lernen? Soll dieses Wissen nicht in Form von Daten in der Tabellenstruktur enthalten sein? Oder wie soll ich mir das konkret vorstellen? Und welche Abfrage ist gemeint, die SQL-Query bereits oder erst ein anschließendes Aufbereiten nach dem Fetchen der Ergebnismenge?

          Ziel also, aus der parent-Beziehung eine Child-Beziehung zu machen und im Ergebnis dessen hast Du eine Datenstruktur im Hauptspeicher, welche rekursiv durchlaufen werden kann.

          Der Teil scheint mir klar zu sein. Aber wo erzeugst du den? Erst im Client? Wenn ja, warum soll denn nicht die Möglichkeit der rekursiven Abfragen direkt im DBMS genutzt werden? Microsofts SQL-Server bietet doch nicht umsonst dafür Unterstützung in Form von Common Table Expressions an.

          Jeder Knoten wird als Objekt betrachtet, in Perl wäre es es ein Hash nach dem EAV-Muster

          Ach herje, jetzt kommt das schon wieder.

          {
            Entity => { Attribute => Value},
            4711   => { childs => [] } # Arrayreferenz auf die Kindknoten
          }
          

          Aber irgendwie entspricht das nicht meinem Verständnis von EAV. Man nimmt das EAV-Modell, weil man damit eine unbekannte Anzahl von Attributen unterschiedlichen Types definieren kann. Das wird hier gar nicht benötigt und so ist das hier einfach nur eine feststehende Datenstruktur, wie man sie auch mit einer starren OOP-Klasse definieren kann.

          Für die Tabelle in einer DB reichen übrigens 3 Felder um eine solche Datenstruktur abbilden zu können.

          Das Binärsystem ist auch recht einfach aufgebaut, einfach zu verstehen und die grundlegenden Verfahren einfach zu implementieren. Es ist so universell verwendbar, dass wir gerade dabei sind, all unser Wissen auf Basis dieses Systems zu speichern, es damit zu verarbeiten und zu transportieren. Und trotzdem verwenden wir beispielsweise für das schriftliche Kommunizieren ein deutlich komplexeres System aus Buchstaben und anderen Zeichen. Das passiert nicht nur aus Tradition, sondern weil es nicht nur in diesem Fall einfacher und praktischer ist, ein spezialisierteres System zu verwenden.

          dedlfix.

  2. Tach!

    Leider liest sie mir den Baum nur teilweise aus (wenn am Verweis wieder ein Verweis hängt kommt der nicht mit).

    Bitte schildere mal konkret, an welcher Stelle das Problem auftritt. Was konkret erwartest du und was erhältst du stattdessen? Und gibt es vielleicht eine Fehlermeldung (die du eventuell nicht abgefragt hast)?

    Manchmal hilft es auch anderen oder so vor sich hin, mal in Worte zu fassen, was genau das Gebilde tut oder tun soll und das möglichst so detailiert, dass der (imaginäre) Andere das ohne großes Vorwissen verstehen kann. Es soll dabei schon vorgekommen sein, dass man dann ins Stocken gerät, weil eine Sache unklar ist, und man so auf das eigentliche Problem gestoßen ist.

    Funktioniert die CTE-Abfrage denn erstmal grundlegend ohne die Erweitung um deine Spezialbedinung? Und funktioniert das Ermitteln der Daten gemäß der Spezialbedingung, wenn man das mal unabhängig testet ohne den Aspekt der verschachtelten Struktur dazuzunehmen, sprich: als Extra-Query ohne die CTE? Es ist günstig, wenn man die Aufabenstellung in kleine unabhängige Teileinheiten herunterbrechen kann, sie einzeln testet und erst dann zusammenfügt.

    Gibt es allenfalls bessere Möglichkeiten die Datenstruktur aufzusetzen?

    Für die Struktur fällt mir als Alternative nur das Nested-Sets-Model ein. Das ist komplexer aufgebaut und nicht so einfach zu pflegen, dafür bietet es eine Menge Möglichkeiten zum Formulieren von Abfragen nach Teilmengen. Aber das ist vielleicht für deinen Fall nicht notwendig. Wenn man die Abfragevielfalt nicht braucht, muss man sich nicht mit der Komplexität rumschlagen. Dann reichen auch die Möglichkeiten der Abfrage mithilfe der CTE.

    Was ich mir aber als Lösung vorstellen kann, wäre, die Ermittlung eines Nodes in eine View oder Table-Valued User-Defined Function auszulagern. Oder wenn nur ein einzelner Wert benötigt wird (z.B. eine ID), dann tuts auch eine einfache Function. Jedenfalls sollte diese den gewünschten Datensatz ermitteln und dabei entweder den direkten Datensatz oder den referenzierten zurückgeben (oder dessen ID). Das kann man dann auch schön separat testen.

    dedlfix.