Tabellenverknüpfungen :/



  • Also, ich habe 3 MySql Tabellen, die folgendermaßen aussehen:

    Kategorien:

    kategorie_id       subkategorie_von
    1                  -1 (hauptkategorie)
    2                  1
    

    Index:

    kategorie_id      eintrag_id
    1                 1
    2                 2
    

    Einträge:

    eintrag_id        datum
    1                 ....
    2                 ....
    

    Dabei können die Tabellen beliebig viele Einträge haben, ich will jetzt aus allen Einträgen, die in dieser Kategorie oder in einer Unterkategorie dieser Kategorie sind, die neusten zwei z.b. 😉

    Ich habe nur die kategorie_id, für die Kategorie, bei der ich das machen möchte.

    Bin nicht so der Profi und weiß auch nicht, ob das Datenbankdesign so optimal ist... 🙄
    Geht darum, einen Webkatalog zu schreiben...

    schonmal vielen dank im voraus!!



  • Ich denke, dass das bei einer Baumstuktur (ist denn die Tiefe begrenzt?) nicht so direkt in einer einzigen Anfrage geht...



  • SELECT .Kategorien,.Index,*.eintrag_id FROM Kategorien k,Index i ,eintrag_id e WHERE k.kategorie_id = wasausimmer AND k.kategorie_id = i.kategorie_id AND e.eintrag_id = i.eintrag_id ORDER BY e.datum DESC/ASC LIMIT 2

    DESC bzw. ASC sortiert aufsteigend oder absteigend. Minn eines davon.

    Diese Fragen gehören aber in Zukunft ins DB-Forum.



  • Hi,

    ich hab das Query mal soweit abgeändert, damit es bei mir keine Fehler mehr verursacht, wobei es jetzt (nicht?) mehr funktioniert (Ich kann es ehrlich gesagt auch nicht richtig nachvollziehen, wie es funktionieren sollte).

    $res = mysql_query("SELECT 
    		wc_categories.*, 
    		wc_entry_index.*, 
    		wc_entries.*
    		FROM 
    		wc_categories, 
    		wc_entry_index, 
    		wc_entries 
    		WHERE 
    		wc_categories.cat_id = $viewcat 
    		AND wc_categories.cat_id = wc_entry_index.cat_id 
    		AND wc_entries.en_id = wc_entry_index.en_id 
    		ORDER BY wc_entries.en_add_date 
    		DESC LIMIT 2");
    

    Da sieht man jetzt die tatsächlichen Tabellennamen, ich hatte das oben nur schnell aus den Gedanken geschrieben, wie es aussieht.
    Das Query liefert auf jeden Fall 0 Treffer zurück, wobei die Tabellen folgendermaßen aussehen:

    wc_categories:
    cat_id     cat_of_id    cat_name
    28         26           UnterUnterkategorie 1
    25         2            Unterkategorie 2
    2          -1           Hauptkategorie
    26         2            Unterkategorie 1
    
    wc_entry_index:
    en_id      cat_id
    1          25
    2          28
    
    wc_entries:
    en_id    en_add_date
    1        ...
    2        ...
    

    Wenn ich jetzt in Kategorie 2 bin, will ich beide Einträge angezeigt haben, weil die beiden ja jeweils auch in dieser Kategorie sind. Wenn ich in Kategorie 25 bin, will ich nur Eintrag 1 angezeigt haben...

    Müssen dafür auch vielleicht irgendwelche Primärschlüssel definiert sein?!

    flenders schrieb:

    (ist denn die Tiefe begrenzt?)

    Theoretisch nicht, der erste Eintrag könnte sich auch erst in Unterkategorie 15.000 befinden...

    Unix-Tom schrieb:

    Diese Fragen gehören aber in Zukunft ins DB-Forum.

    Jo, sry, bin halt direkt hier reingestürzt, weil ich nicht wusste, ob man das direkt mit einem Query lösen kann, oder ob man auch PHP usw. braucht...



  • MySql-Doofi schrieb:

    flenders schrieb:

    (ist denn die Tiefe begrenzt?)

    Theoretisch nicht, der erste Eintrag könnte sich auch erst in Unterkategorie 15.000 befinden...

    Unix-Tom schrieb:

    Diese Fragen gehören aber in Zukunft ins DB-Forum.

    Jo, sry, bin halt direkt hier reingestürzt, weil ich nicht wusste, ob man das direkt mit einem Query lösen kann, oder ob man auch PHP usw. braucht...

    Ja, also dann wirst du das wohl auch mit PHP o.ä. arbeiten müssen und brauchst viele Abfragen (würde ich zumindest denken). Ich kann mir prinzipiell 2 unterschiedliche Ansätze denken:

    - 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

    - von oben nach unten durcharbeiten und eine Liste mit allen IDs erstellen, die sich im Baum unterhalb der gesuchten ID befinden und damit suchen

    Wie man dies jedoch bei deinem DB-Aufwand einigermaßen sinnvoll / schnell implementiert, kann ich dir nicht sagen. Ich verschieb dich mal ins DB-Forum - vielleicht hat dort ja noch jemand eine gute Idee 🙂



  • Dieser Thread wurde von Moderator/in flenders aus dem Forum Webzeugs in das Forum Datenbanken verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • 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.


Log in to reply