Pit: Query Grundsatzfrage

Hallo,

ich habe 2 Tabellen:

Tabelle M:

id|R_id|MID |-|-|-|- |1|100|10 |2|103|11 |3|300|10 |4|110|12 |5|140|11

Tabelle M_Container:

MID|Art|Datum |-|-|-|- |10|PDF|2019-02-02 |11|PDF|2019-02-03 |12|TXT|2019-02-04 |13|PDF|2019-02-05 |14|MAIL|2019-02-06

Nun hätte ich gerne eine Abfrage, die geeignet dafür wäre, ein Ergebnis in dieser Art umzusetzen:

MID R_id Art Datum
10 100,300 PDF 2019-02-02
11 103,140 PDF 2019-02-03
12 110 PDF 2019-02-04
13 PDF 2019-02-05
14 PDF 2019-02-06

Wie ging das nochmal? Irgendwann hatte ich so ein Problem schonmal und meine, ich hätte da irgendwie mit CONCAT oder GROUP_CONCAT in einer correlated Subquery gearbeitet. Aber ich finde es weder, noch fruchten meine Versuche hiermit.

Kann mir mal bitte wer auf die Sprünge helfen, wie man grundsätzlich bei einer solchen Konstellation vorgeht?

Mit Konstellation meine ich alle Beispiele, die darauf hinaus laufen, dass 2 GTabellen sich 1:n verhalten, also 1 Trainer - n Spieler, 1 Obstbaum - n Früchte, 1 TV-Sender - viele Sendungen. Und ich möchte dann eine Tabelle erstellen, bei der z.b alle Trainer mitsamt ihren Spielern oder eine aufzählung aller Obstbäume einer Wiese mitsamt den Früchten oder alle TV-Sender mitsamt ihren Sendungen enthält.

Kriegt man das sql-seitig gebacken oder muss man in jedem Fall noch seine Programmiersprache bemühen? Ich vermute, in meinen 3 Tabellen reicht sql hierfür aus. Aber wie sieht es z.b. bei dem Trainer-Beispiel aus, wenn ich zu den Spielern selber noch viele weitere Daten wie Personendaten, Wohnortdaten, frühere Vereine, usw. aufzählen möchte. Klappt das auch noch mit SQL alleine oder nicht mehr?

Pit

  1. Tach!

    Irgendwann hatte ich so ein Problem schonmal und meine, ich hätte da irgendwie mit CONCAT oder GROUP_CONCAT in einer correlated Subquery gearbeitet.

    Nach dem Joinen gruppieren, und mit GROUP_CONCAT() bekommst du die Werte zum Beispiel kommasepariert aneinandergehängt. Das ergibt einen String und für eine Weiterverarbeitung müssten die Werte erst mit explode() (wenn wir PHP annehmen) separieren werden.

    Alternativ nicht im DBMS gruppieren und stattdessen im abfragenden Programm die zusammengehörigen Werte (je MID in deinem Fall) zusammenführen.

    Kriegt man das sql-seitig gebacken oder muss man in jedem Fall noch seine Programmiersprache bemühen? Ich vermute, in meinen 3 Tabellen reicht sql hierfür aus. Aber wie sieht es z.b. bei dem Trainer-Beispiel aus, wenn ich zu den Spielern selber noch viele weitere Daten wie Personendaten, Wohnortdaten, frühere Vereine, usw. aufzählen möchte. Klappt das auch noch mit SQL alleine oder nicht mehr?

    Wenn mehr Werte zusammenzufassen sind, dann wird mit mehreren GROUP_CONCAT()s die Folgeverarbeitung komplexer. Dann kommt man mit Gruppenwechsel-Technik angenehmer weiter, also mit Zusammenführung der Dateien per Gruppe in der das SQL befragenden Umgebung.

    dedlfix.

    1. Hallo dedlfix,

      danke für Deine Antwort. Und warum nicht immer Gruppenwechseltechnik? (Danke für den Begriff, den kannte ich nicht, obwohl ich die Technik schon oft angewendet hatte)

      Tabelle marke

      +----+-------+
      | id | name  |
      +----+-------+
      |  1 | Audi  |
      |  2 | VW    |
      |  3 | Skoda |
      |  4 | Seat  |
      +----+-------+
      

      Tabelle modell

      +----+---------+----------+
      | id | name    | marke_id |
      +----+---------+----------+
      |  1 | A2      |        1 |
      |  2 | A4      |        1 |
      |  3 | A6      |        1 |
      |  4 | Golf    |        2 |
      |  5 | Sharan  |        2 |
      |  6 | Touareg |        2 |
      |  7 | Octavia |        3 |
      |  8 | Fabia   |        3 |
      |  9 | Yeti    |        3 |
      | 10 | Leon    |        4 |
      | 11 | Ibiza   |        4 |
      +----+---------+----------+
      
      
      SELECT
        ma.name AS marke_name,
        mo.name AS modell_name
      FROM marke ma
      INNER JOIN modell mo
        ON ma.id = mo.marke_id
      ORDER BY ma.name, mo.name
      
      +------------+-------------+
      | marke_name | modell_name |
      +------------+-------------+
      | Audi       | A2          |
      | Audi       | A4          |
      | Audi       | A6          |
      | Seat       | Ibiza       |
      | Seat       | Leon        |
      | Skoda      | Fabia       |
      | Skoda      | Octavia     |
      | Skoda      | Yeti        |
      | VW         | Golf        |
      | VW         | Sharan      |
      | VW         | Touareg     |
      +------------+-------------+
      

      Nun wird die Ausgabe mittels Gruppenbruch in eine lesbar gegliederte Form gebracht.

      $last_entry = null;
      
      while ($row = $result->fetch_object()) {
      
         if ($last_entry != $row->marke_name) {
              echo $row->marke_name.'<br>';
              $last_entry = $row->marke_name;
          }
          echo '- '.$row->modell_name.'<br>';
      }
      

      Ausgabe:

      
      Audi
      - A2
      - A4
      - A6
      Seat
      - Ibiza
      - Leon
      Skoda
      - Fabia
      - Octavia
      - Yeti
      VW
      - Golf
      - Sharan
      - Touareg
      

      Quelle: https://php-de.github.io/jumpto/gruppenbruch/

      Pit

      1. Hallo Pit,

        autsch. Gruppenbruch ist Denglisch, und dazu noch blödsinniges. Wenn man das break in control break schon beibehalten will, dann sollte man es auch vollständig tun und von einer Steuerungsunterbrechung sprechen, in dem Sinne, dass man in der Steuerung des Programmablaufs eine Unterbrechung vorsieht, um den Übergang von einem Schlüsselbegriff zum anderen zu behandeln.

        Das deutsch Wort Gruppenwechsel beschreibt das meiner Meinung nach viel besser als das englische control break[1].

        In einfachen Fällen, wenn Du tatsächlich nur eine Liste von Werten kommasepariert in einer Liste haben willst und diese Liste nicht wieder zerlegen musst, ist GROUP_CONCAT() die einfachere Lösung und erspart Dir Arbeit im PHP.

        In deiner Problemstellung würde das hinreichen. Du solltest dann aber erst gruppieren und dann joinen, das reduziert die Arbeit im Server (es sei denn der Optimizer kriegt das selbst hin - EXPLAIN it).

        SELECT MID, R_ids, Art, Datum
        FROM (SELECT MID, GROUP_CONCAT(R_id) as R_ids 
                 FROM marke GROUP BY MID) m
          JOIN M_Container c ON m.MID=c.MID
        

        sollte effizienter sein als

        SELECT MID, GROUP_CONCAT(R_id), Art, Datum
        FROM marke m
          JOIN M_Container c ON m.MID=c.MID
        GROUP BY MID, Art, Datum
        

        Das Folgende geht übrigens auch:

        SELECT MID,
               (SELECT GROUP_CONCAT(R_id) 
                  FROM marke m 
                  WHERE m.MID=c.MID) as R_id,
               Art,
               Datum
        FROM  M_Container c
        

        Und ja: Alle Spalten, die nicht aggregiert werden, müssen im GROUP BY aufgelistet werden. MYSQL würde es auch zulassen, nur MID im GROUP BY anzugeben und in diesem Fall würde das sogar zu einem deterministischen Ergebnis führen. Tatsächlich ist es aber ungültiges SQL und kann zu merkwürdigen Fehlern führen, wenn MID in der M_Container Tabelle nicht unique ist. Man gewöhnt es sich am besten gar nicht erst an.

        Rolf

        --
        sumpsi - posui - clusi

        1. und das müsste ja seit deutscher Tastaturen sowieso eigentlich String-Unterbrechung heißen! ↩︎

        1. Hallo Rolf,

          In einfachen Fällen, wenn Du tatsächlich nur eine Liste von Werten kommasepariert in einer Liste haben willst und diese Liste nicht wieder zerlegen musst, ist GROUP_CONCAT() die einfachere Lösung und erspart Dir Arbeit im PHP.

          Stimmt. Im konkreten Fall habe ich es zwar doch noch etwas anders gemacht, aber prinzipiell sehe ich das genauso wie Du.

          In deiner Problemstellung würde das hinreichen. Du solltest dann aber erst gruppieren und dann joinen, das reduziert die Arbeit im Server (es sei denn der Optimizer kriegt das selbst hin - EXPLAIN it).

          SELECT MID, R_ids, Art, Datum
          FROM (SELECT MID, GROUP_CONCAT(R_id) as R_ids 
                   FROM marke GROUP BY MID) m
            JOIN M_Container c ON m.MID=c.MID
          

          sollte effizienter sein als

          SELECT MID, GROUP_CONCAT(R_id), Art, Datum
          FROM marke m
            JOIN M_Container c ON m.MID=c.MID
          GROUP BY MID, Art, Datum
          

          Werde ich mal spaßeshalber prüfen 😀 Muß aber bis zum WE warten...

          Das Folgende geht übrigens auch:

          Und ja: Alle Spalten, die nicht aggregiert werden, müssen im GROUP BY aufgelistet werden. MYSQL würde es auch zulassen, nur MID im GROUP BY anzugeben und in diesem Fall würde das sogar zu einem deterministischen Ergebnis führen. Tatsächlich ist es aber ungültiges SQL und kann zu merkwürdigen Fehlern führen, wenn MID in der M_Container Tabelle nicht unique ist. Man gewöhnt es sich am besten gar nicht erst an.

          Du hast Recht. In mienem Fall war zwar die MID der primary Index autoincrement, aber grundsätzlich müssen alle Spalten, die nciht aggregiert werden ins GROUP BY.

          Danke für Deine Antwort und viele Grüße,

          Pit

      2. Tach!

        Und warum nicht immer Gruppenwechseltechnik?

        Die Frage beantwortet sich von selbst, wenn du einen entsprechenden Anwendungsfall hast, und merkst / entscheidest, dass eine andere Technik dafür besser geeignet ist.

        $last_entry = null;
        
        while ($row = $result->fetch_object()) {
        
           if ($last_entry != $row->marke_name) {
                echo $row->marke_name.'<br>';
                $last_entry = $row->marke_name;
            }
            echo '- '.$row->modell_name.'<br>';
        }
        

        Bei Ausgaben ist der Kontextwechsel zu beachten: htmlspecialchars(). Vermutlich kannst du nicht garantieren, dass im DBMS lediglich harmlose Werte (hier im Sinne von HTML) stehen.

        dedlfix.