Michael: hunderte Kategorien Abspeichern und ausgeben

Ich beschäftige mich gerade damit Kategorien abzuspeichern. Es werden voraussichtlich weit über 1000 Stück werden. Diese würde ich gerne in eine mysqli Datenbank schreiben.

ID zuordnung name

01 – 00 – Start
02 – 01 – Deutschland
03 – 01 – Österreich
04 – 02 – Bayern
05 - 04 – Niederbayern
06 – 05 – München
07 – 06 – Schwabing
08 – 03 – Oberösterreich

Ausgabe - Beispiel

Start – Deutschland – Bayern – Niederbayern – München – Schwabing

Gibt es noch eine Möglichkeit dies zu tun?

Mit den besten Grüssen

Michael

  1. Tach!

    Start – Deutschland – Bayern – Niederbayern – München – Schwabing

    Gibt es noch eine Möglichkeit dies zu tun?

    Wenn das "dies" sich auf die Möglichkeiten der Speicherun beziehen, dann gibt es neben der einfachen Parent-Beziehung auch noch Nested Sets. Das ist bei Änderungen aufwändiger als das Verwalten des Parents, aber man hat dann auch eine Menge Möglichkeiten beim Abfragen.

    dedlfix.

    1. Nested Sets - eine elegante Idee, um rekursive Queries zu vermeiden. Algorithmisch aber durchaus anspruchsvoll, vor allem was einen performanten Multiuserbetrieb angeht.

      Seit sich die rekursiven Queries in der Breite durchsetzen (selbst MySQL kann es seit Version 8) relativiert sich der Vorteil von Nested Sets aber wieder.

      Rolf

      --
      sumpsi - posui - clusi
      1. Ich habe es jetzt mal mit Nested versucht, danke erstmal für den Tipp. Aber ich denke ich habe da einen Wurm drin. Denn die Abfrage dauert doch sehr lange. Es sind 1400 Datensätze und die Abfrage dauert 5 Sekunden.

        Um es einfach zu sagen, ich denke ich habe einen Knoten in der Abfrage.

        
        SELECT 
        n.id, 
        n.name, 
        COUNT(*)-1 AS ebene 
        FROM 
        kategorien AS n,
        kategorien AS p 
        WHERE 
        n.lft BETWEEN p.lft 
        AND 
        p.rgt 
        GROUP BY n.lft 
        HAVING ebene=1 
        ORDER BY n.lft
        
        1. Hallo,

          Warum zeichnest du SQL-Code als PHP-Code aus? Ich hab das mal geändert.
          Dabei fiel mir auf, wenn man den Schnipsel statt als SQL als MySQL auszeichnet, wird das Schlüsselwort COUNT rot gefärbt. Vielleicht hat's damit zu tun?

          Gruß
          Kalk

          1. Hallo Tabellenkalk,

            nö, das ist ein Highlighter, kein Parser. Der MySQL Highlighter macht scheinbar jedes Wort rot, das vor einer Klammer-auf steht; es sei denn, das Wort ist ein SQL Schlüsselwort. Dann macht er es fett. SQL Syntax ist ihm ansonsten ziemlich wurscht, wie sich hier zeigt:

            select breakfast, min(age), max(size)
            from ham and eggs
            where (bacon (sic!) is crisp)
            

            Rolf

            --
            sumpsi - posui - clusi
        2. Hallo Bernd. Oder bist Du Michael?

          verstehe ich das richtig? Die Query zählt für jede Kategorie, in wievielen Ober-Ebenen sie enthalten ist (wobei sie sich selbst mitzählt; X BETWEEN A AND B ist ein A <= X <= B Vergleich), und filtert dann die, wo der Zähler den Wert 2 annimmt. Du würdest hier also die Kategorien der zweiten Ebene bestimmen.

          Allerdings ist dieses Statement unglaublich komplex. Der Server muss hier pro Kategorie (Zeile aus n) alle Kategorien suchen, die sie enthalten. Mit einem Index auf (lft,rgt) könnte das als Index-Scan laufen, sonst ist es ein Table Scan. Hast Du einen Index?

          Es ist allerdings auch nicht einfach, auf anderem Weg die Kategorien der zweiten Ebene zu finden. "Direkte Kinder suchen" ist eine Übung, mit der sich Nested Sets schwer tun, weil sie auf Tiefensuche optimiert sind. Wenn du den Pfad finden willst, der von einem Knoten zu seinem Wurzelknoten des Kategorienbaums führt, dann kannst Du das mit einer ähnlichen Query wie oben machen:

          SELECT id, name
          FROM kategorien
          WHERE lft < :leafLeft AND rgt > :leafRight
          ORDER NY lft
          

          Deine Query von oben führt diese Pfadsuche für JEDE Kategorie aus und wählt die mit einer Pfadlänge von 2. Das ist sehr langsam.

          Also – wie immer stellt sich die Frage nach dem EIGENTLICHEN Problem. Willst Du direkte Kindknoten finden? Willst Du die Knoten der zweiten Ebene finden? Ich denke, diese beiden Übungen sind in einem SQL Statement kaum performant lösbar (es sei denn du hast rekursive Queries, aber die wolltest Du ja gerade vermeiden). Es könnte helfen, wenn Du außer den Nested Set Intervallgrenzen noch an jeder Kategorie die ID der Eltern-Kategorie speicherst, dann kannst Du für eine Kind-Abfrage darüber gehen und hast sozusagen beide Welten vereint.

          In einem in Wikipedia verlinkten Text zu Nested Sets habe ich übrigens noch den Hinweis gefunden, dass es ineffizient ist, die Tabelle als ein einziges Nested Set aufzubauen. Bei Updates muss man dann sehr viel ändern. Statt dessen wurde dort vorgeschlagen, an jedem Knoten noch eine sogenannte Root-ID zu speichern und allen Kindern dieses Knotens diese Root-ID mitzugeben. Bei 20 Kategorien auf Top-Level hättest Du dann nicht ein Nested Set, sondern 20.

          D.h. die Optimierung besteht darin, pro Zeile fünf Verwaltungsinformationen zu führen:

          • ID
          • RootID
          • ParentID
          • Lft
          • Rgt

          Und je nach Aktion nutzt du die sinnvollste Kombi davon.

          ID Root Parent Lft Rgt Name
          1 1 0 1 10 Deutschland
          2 1 1 2 9 Nordrhein-Westfalen
          3 1 2 3 4 Köln
          4 1 2 5 6 Düsseldorf
          5 1 2 7 8 Münster
          6 6 0 1 14 Schweiz
          7 6 6 2 7 Wallis
          8 6 7 3 4 Zermatt
          9 6 7 5 6 Brig
          10 6 6 8 13 Graubünden
          11 6 10 9 10 St. Moritz
          12 6 10 11 12 Chur

          Rolf

          --
          sumpsi - posui - clusi
  2. hallo

    Ich beschäftige mich gerade damit Kategorien abzuspeichern. Es werden voraussichtlich weit über 1000 Stück werden. Diese würde ich gerne in eine mysqli Datenbank schreiben.

    ID zuordnung name

    01 – 00 – Start
    02 – 01 – Deutschland
    03 – 01 – Österreich
    04 – 02 – Bayern
    05 - 04 – Niederbayern
    06 – 05 – München
    07 – 06 – Schwabing
    08 – 03 – Oberösterreich
    

    Ausgabe - Beispiel

    Start – Deutschland – Bayern – Niederbayern – München – Schwabing

    Der Ort Laufen existiert in der Schweiz gleich mehrfach. Für eine ID müsstest du schon die Parent-ID mit angeben.

    1. Hallo beatovich,

      nicht nur Laufen

      Die beiden in Niedersachsen liegen Luftlinie keine 50km voneinander entfernt. Dieser Umstand hat meine Eltern bei einer Radtour schon einmal sehr ermüdet...

      Rolf

      --
      sumpsi - posui - clusi
      1. hallo

        Hallo beatovich,

        nicht nur Laufen

        Die beiden in Niedersachsen liegen Luftlinie keine 50km voneinander entfernt. Dieser Umstand hat meine Eltern bei einer Radtour schon einmal sehr ermüdet...

        Rolf

        sumpsi - posui - clusi

        Ja, es gibt praktisch keine einzigartigen Ortsnamen.

        Im übrigen habe ich übersehen, dass er das von mir vorgeschlagene ja umsetzt. sorry!

        1. Hej beatovich,

          Ja, es gibt praktisch keine einzigartigen Ortsnamen.

          Hommingberg! 😉

          Spezialität: Hommingberger Gepardenforelle!

          Marc

          1. Hommingberg! 😉

            Spezialität: Hommingberger Gepardenforelle!

            Müssen wir doch nochmal Columbus losschicken?

  3. hi Michael,

    zum Abbilden einer Hierarchie genügen 3 Felder:

    Identifier|Attribut|Wert

    Wobei eines der Attribute der Parent ist. Über weitere Attribute kann man z,B, die Identifier weiter kategorisieren, z.b. in Länder, Gebiete, Zonen usw. Ich hatte solche Tabellen schon mit mehr als 300T Einträgen, mit einem Index auf die Id sind Abfragen absolut performant.

    Natürlich kannst Du die Attribute auch in dedizierten Feldern vorhalten, z.B. so

    id|name|parent_id|zone_id|is_root

    wobei is_root die Wurzel einer bestimmten Zone taggt. Die Rekursion die zum Darstellen gebraucht wird, erfolgt mit dem Abfrageergebnis im Hauptspeicher.

    MfG