Harlequin: Bäume aus Datenbank selektieren

Yerf!

Ich versuch gerade Navigationsbäume aus einer Datenbank zu selektieren, sprich: eine baumförmige Struktur hinter der IDs stecken, die als Fremdschlüssel in einer anderen Tabelle auftauchen. Das ganze sollte allgemein funktionieren, zumindest aber in Oracle 10 und Access.

Ist aber auch egal... eigentliches Problem:

Ich möchte das in einer einfachen und einheitlichen Struktur machen.

Idee: Ich liefere 3 Spalten zurück: ID, Name und ParentID

Das lässt sich gut verarbeiten und (eigentlich) einfach erstellen.

Problem: die Struktur in der Datenbank... die verschiedenen Ebenen der Baumstruktur liegen in unterschiedlichen Tabellen und die ID-Bereiche überschneiden sich dementsprechend. Wenn ich das per union zusammenfasse bekomme ich doppelte IDs.

select COUNTRIES.CountryID as ID, ShortName + "" - "" + LongName as Name, RegionID as ParentID from COUNTRIES  
left join REGIONS_COUNTRIES on COUNTRIES.CountryID = REGIONS_COUNTRIES.CountryID  
union all  
select REGIONS.RegionID as ID, ShortName as Name, null as ParentID from REGIONS

Die resultierende ID-Spalte wird mit Country-IDs und Region-IDs gefüllt, das führt zur Überschneidung. Die Parent-Child-Zuordnung geht damit auch nicht mehr sauber.

Wie löst man das am besten? Oder sollte ich mich von der Idee einer allgemeinen Lösung für beliebig tiefe Bäume verabschieden und für jeden Fall eine Speziallösung bauen (z.B. mit Spalten für die Ebenen)?

Gruß,

Harlequin

--
RIP --- XHTML 2
nur die Besten sterben jung
  1. Nested Set
    Bei Google findet man gute Tutorials dies bezüglich.

    Gruß
    T

    Rex

    1. Yerf!

      Hab ich mir schon angeschaut, hilft mir aber leider nicht weiter... das Problem ist, die jetzige Datenstruktur ist gegeben, an der kann ich nichts ändern (wäre auch nicht sinnvoll). Bei jeder Abfrage ein neues Nested-Set zu erstellen wäre aber auch Overkill (gerade das Erstellen ist da ja sehr aufwändig)

      Gruß,

      Harlequin

      --
      RIP --- XHTML 2
      nur die Besten sterben jung
  2. Wie löst man das am besten? Oder sollte ich mich von der Idee einer allgemeinen Lösung für beliebig tiefe Bäume verabschieden und für jeden Fall eine Speziallösung bauen (z.B. mit Spalten für die Ebenen)?

    https://forum.selfhtml.org/?t=207823&m=1412968

    1. Yerf!

      Hm, ich muss glaub ich nochmal etwas weiter ausholen... einen Nested Set hilft mir nämlich nicht wirklich weiter.

      Die jetzige Datenbanstruktur ist so gegeben, dass die verschiedenen Ebenen der Struktur in unterschiedlichen Tabellen liegen. Das ist so auch sinnvoll, da es sich um Unterschiedliche Entitäten handelt (siehe Beispiel: Länder und Regionen, gibt aber auch noch andere Fälle)

      Ich möchte jetzt "nur" das ganze über einen Selekt auslesen um damit einen Baum in der GUI zu füttern und zwar möglichst generisch für die verschiedenen Strukturen, so das ich nur das SQL austauschen muss, die Programmlogik aber die gleiche bleiben kann. Deswegen sollte die Anzahl der Ebenen möglichst beliebig sein.

      Aber ich befürchte das sich das so nicht lösen lässt...

      Gruß,

      Harlequin

      --
      RIP --- XHTML 2
      nur die Besten sterben jung
      1. Aber ich befürchte das sich das so nicht lösen lässt...

        In dem dort verlinkten Artikel ist der Weg für die Eltern-Kind-Sache genauso beschrieben - das ist es doch, was du willst?

        1. Yerf!

          In dem dort verlinkten Artikel ist der Weg für die Eltern-Kind-Sache genauso beschrieben - das ist es doch, was du willst?

          Hm, also ich bin nochmal über den Artikel, aber irgendwie gehen die immer davon aus, das alle Ebenen in der selben Datenbanktabelle liegen. Das ist ja bei mir gerade nicht der Fall. Ich will sie nur im Abfrageergebnis zusammenführen, bekomme dann aber Probleme mit den IDs.

          Aber ich bin nochmal über die Fälle die ich so hab drüber und hab grad Tabellen gefunden die in sich eine Baumstruktur haben... damit kann ich das mit der allgemeinen Lösung eh vergessen, wenn ich so einen Mischmasch hab :-(

          Ich werd das ganze in C# abhandeln. Ich bau mir im Modell nen generischen Zugriff auf die verschiedenen Tabellentypen (eine Schicht, Baumstruktur, etc.) und bau mir dann Views für die einzelnen Bäume die die Daten aus den einzelnen Tabellen zusammenführen. Die verknüpf ich dann mit der GUI.

          Gruß,

          Harlequin

          --
          RIP --- XHTML 2
          nur die Besten sterben jung
          1. Tach!

            In dem dort verlinkten Artikel ist der Weg für die Eltern-Kind-Sache genauso beschrieben - das ist es doch, was du willst?
            Hm, also ich bin nochmal über den Artikel, aber irgendwie gehen die immer davon aus, das alle Ebenen in der selben Datenbanktabelle liegen. Das ist ja bei mir gerade nicht der Fall.

            Die machen da einen Self-Join, du machst dann eben einen Join zu einer anderen Tabelle

            Ich will sie nur im Abfrageergebnis zusammenführen, bekomme dann aber Probleme mit den IDs.

            Gleiche IDs mit unterschiedlicher Bedeutung können mit einem zusätzlichen Kriterium ihre Eindeutigkeit wiedererlangen. CONCAT('x', id) AS xid und CONCAT('y', id) AS yid wäre eine Möglichkeit.

            Aber ich bin nochmal über die Fälle die ich so hab drüber und hab grad Tabellen gefunden die in sich eine Baumstruktur haben... damit kann ich das mit der allgemeinen Lösung eh vergessen, wenn ich so einen Mischmasch hab :-(
            Ich werd das ganze in C# abhandeln. Ich bau mir im Modell nen generischen Zugriff auf die verschiedenen Tabellentypen (eine Schicht, Baumstruktur, etc.) und bau mir dann Views für die einzelnen Bäume die die Daten aus den einzelnen Tabellen zusammenführen.

            Dann vielleicht doch lieber alles im Entity Framework (oder was auch immer du verwendest) abhandeln.

            dedlfix.

            1. Yerf!

              Die machen da einen Self-Join, du machst dann eben einen Join zu einer anderen Tabelle

              Gut, soweit war ich aber schon am Eingangsposting.

              Ich will sie nur im Abfrageergebnis zusammenführen, bekomme dann aber Probleme mit den IDs.

              Gleiche IDs mit unterschiedlicher Bedeutung können mit einem zusätzlichen Kriterium ihre Eindeutigkeit wiedererlangen. CONCAT('x', id) AS xid und CONCAT('y', id) AS yid wäre eine Möglichkeit.

              Ja, sowas in der Art hab ich mir auch überlegt, aber dabei Fehlermeldungen aus der Datenbankklasse bekommen (WTF?), weiß nicht ob ich dem weiter nachgehen soll... so wie es aussieht komm ich so eh nicht ans Ziel.

              Dann vielleicht doch lieber alles im Entity Framework (oder was auch immer du verwendest) abhandeln.

              Framework gibts nicht... ich hab nur eine gegebene Datenbankabstraktion (für Select, Insert usw.), der Rest ist Eigenbau. Aber du würdest die Logik komplett ins Modell (ausgehend von MVC) bauen? (Ok, mit meinem ersten Ansatz wäre sie auch dort gewesen)

              Gruß,

              Harlequin

              --
              RIP --- XHTML 2
              nur die Besten sterben jung
              1. Tach!

                Framework gibts nicht... ich hab nur eine gegebene Datenbankabstraktion (für Select, Insert usw.), der Rest ist Eigenbau. Aber du würdest die Logik komplett ins Modell (ausgehend von MVC) bauen? (Ok, mit meinem ersten Ansatz wäre sie auch dort gewesen)

                Ich kann mich ohne genaueres Hinschauen nicht entscheiden, was ich machen würde. Mit so einer komplexen Struktur würde ich vielleicht auch erst ein paar Versuche in verschiedenen Richtungen anstellen, um zu sehen, was sich besser macht.

                Mit einem ORM lassen sich ja die Beziehungen zwischen den Datenbank-Entitäten abbilden. Statt Fremdschlüsseln hat man in seinem Objekt Eigenschaften die auf weitere Objekte verweisen, die für die Einträge aus anderen Tabellen stehen - oder auch reflexiv auf die eigene Tabelle verweisen. Wenn man sich an diesen Eigenschaften entlanghangelt, macht der ORM im Hintergrund üblicherweise Lazy Fetching, also holt sich die Daten bei Bedarf. Das ist nicht besonders toll für das DBMS und die Performance, weil damit viele Einzelabfragen erzeugt werden. Massendatenabfragen machen sich im Verhältnis dazu mit Joins im DBMS günstiger.

                Es kommt nun drauf an, was du für Anwendungsfälle du vorliegen hast - oder anders gefragt, ob sich das Lazy-Fetching nachteilig bemerkbar macht und du doch lieber auf eine DBMS-günstigere Vorgehensweise setzen solltest. Zur Not kannst du die Komplexität von Abfragen immer noch in Views und Stored Procedures verstecken.

                dedlfix.

                1. Yerf!

                  Ein ORM wär vermutlich ne feine Sache, aber hab ich leider nicht. Selber schreiben wird da wohl zu aufwändig und ein "fertiges" lässt sich wohl so ohne weiteres nicht über eine fertige Datenstruktur stülpen. Aber die Datenbank ist fix und wird auch von anderen Tools benutzt.

                  Ich habs im Moment so aufgebaut:

                  • Data-Layer: kapselt die Datenbankzugriffe und das SQL und liefert DataTables zurück
                  • View: bereitet die Daten für einen bestimmten Zweck in internen C# Klassen auf (DataView, Dictionary, Tree)
                  • Forms: Oberfläche, die die Datenobjekte mit den Controls verknüpft

                  Von daher werd ichs so aufbauen, dass der Datalayer Tree-Fragmente liefert (aus immer nur das, was in einer Tabelle steht) und der View baut dann aus x solchen Fragmenten (je nach Tiefe) seine Tree-Struktur individuell (je nach dem welcher Tree) zusammen.

                  Das ganze sollte flexibel genug und ohne zu viel Overhead sein.

                  Gruß,

                  Harlequin

                  --
                  RIP --- XHTML 2
                  nur die Besten sterben jung
                  1. Tach!

                    Ein ORM wär vermutlich ne feine Sache, aber hab ich leider nicht. Selber schreiben wird da wohl zu aufwändig und ein "fertiges" lässt sich wohl so ohne weiteres nicht über eine fertige Datenstruktur stülpen. Aber die Datenbank ist fix und wird auch von anderen Tools benutzt.

                    Das Entity Framework lässt sich sehr gut über eine fertige Datenstruktur stülpen. Am besten geht das über eine MS SQL-Datenbank mit ordentlich deklarierten Fremdschlüsselbeziehungen. Diese kann das EF auswerten und erzeugt sich daraus die Objekt-Struktur. Und wenn du mit ASP.NETs MVC arbeitest, ist das EF dafür quasi maßgeschneidert. Aber auch mit dem guten alten Dataset lässt sich eine vorhandene Struktur einlesen. Andere ORMs kenne ich nicht.

                    Du machst die jedenfalls nichts kaputt, wenn du mal ein "ADO.NET Entity Data Model" (auch zu erkennen an der Endung .edmx) in dein Projekt nimmst und das mal anschaust. Vorher eine Kopie vom Projekt erstellen kann trotzdem nicht schaden, ich weiß grad nicht, ob das EF-Generiertool vorhandene Klassendateien überschreibt oder nicht. Allerdings kann man dem auch vorbeugen, indem man das EF-Item in einem Unterordner erstellt. Dokumentation zum EF sollte sich genügend finden lassen, es gibt auch ein separates Blog vom EF-Team.

                    dedlfix.

                    1. Yerf!

                      Das Entity Framework lässt sich sehr gut über eine fertige Datenstruktur stülpen. Am besten geht das über eine MS SQL-Datenbank mit ordentlich deklarierten Fremdschlüsselbeziehungen. Diese kann das EF auswerten und erzeugt sich daraus die Objekt-Struktur. Und wenn du mit ASP.NETs MVC arbeitest, ist das EF dafür quasi maßgeschneidert. Aber auch mit dem guten alten Dataset lässt sich eine vorhandene Struktur einlesen. Andere ORMs kenne ich nicht.

                      Hm, die Datenbank ist Oracle, soll aber auch als Access-Dump ansprechbar sein... ASP.NET MVC benutz ich auch nicht, hab MVC eher als Muster erwähnt, an das ich mich grob halte.

                      Aber trotzdem danke, ich werds mir mal anschauen.

                      Gruß,

                      Harlequin

                      --
                      RIP --- XHTML 2
                      nur die Besten sterben jung
  3. Yerf!

    Ich hab das ganze jetzt so gelöst, das ich statt dem UNION die Tabellen einzel selektiere und das ganz in C# zusammenbaue. Die ID-Überschneidung umgehe ich durch 2 getrennte Felder für interne und externe Parents.

    Allerdings hab ich noch ein kleines anderes Problem... beim aufbauen des Trees in C# durchlaufe ich die Liste der DataRows und schau, ob der Parent schon im Tree ist:

    • wenn ja: füge als Kind hinzu und merken, das der Knoten eingefügt wurde (damit er später als Parent gefunden wird)
    • wenn nein: in eine Liste parken

    Danach wiederhole ich das ganze mit der merkliste, bis eine Einträge mer drin landen.

    Problem: wenn ich einen Knoten ohne Parent bekomme hab ich eine Endlosschleife...

    Ich bin gerade am rätseln, wie ich das löse. Wie kann ich sowas merken, Brute-Force vorher durchscannen oder gehts schlauer?

    Gruß,

    Harlequin

    --
    RIP --- XHTML 2
    nur die Besten sterben jung
    1. Yerf!

      Ich bin gerade am rätseln, wie ich das löse. Wie kann ich sowas merken, Brute-Force vorher durchscannen oder gehts schlauer?

      Einfach vergleichen wieviele Nodes ich vorher und nachher hab... wenns gleich viele sind konnte ich keinen mehr hinzufügen und kann abbrechen...

      Gruß,

      Harlequin

      --
      RIP --- XHTML 2
      nur die Besten sterben jung