Bernd: LEFT JOIN auf zwei Spalten

Hallo,

kann ein LEFT JOIN auf zwei Spalten innerhalb von einer Tabelle angewendet werden? Meine Idee war

LEFT JOIN todo_bereich ON (todo_bereich.tb_code = todo_grunddaten.bereichSender AND todo_bereich.tb_code = todo_grunddaten.bereichEmpfaenger)

leider bleibt die Ausgabe von tb_titelkurz leer. Eine Fehlermeldung erhalte ich auch keine.

akzeptierte Antworten

  1. Tach!

    kann ein LEFT JOIN auf zwei Spalten innerhalb von einer Tabelle angewendet werden?

    Ja.

    LEFT JOIN todo_bereich ON (todo_bereich.tb_code = todo_grunddaten.bereichSender AND todo_bereich.tb_code = todo_grunddaten.bereichEmpfaenger)
    

    leider bleibt die Ausgabe von tb_titelkurz leer. Eine Fehlermeldung erhalte ich auch keine.

    Ist ja auch kein Fehler, nur logisch falsch. todo_bereich.tb_code kann einen bestimmten Wert oder einen anderen Wert enthalten, aber nicht gleichzeitig zwei verschiedene.

    dedlfix.

    1. Hallo,

      Ist ja auch kein Fehler, nur logisch falsch. todo_bereich.tb_code kann einen bestimmten Wert oder einen anderen Wert enthalten, aber nicht gleichzeitig zwei verschiedene.

      Ok, dann muss ich es etwas genauer erklären. In meiner Tabelle todo_grunddaten sehen die zwei Spalten so aus

      Meine Tabelle todo_bereich sieht so aus

      Mit dem LEFT JOIN würde ich jetzt gerne auf die zweite Tabelle zugreifen um die Werte auszulesen.

      1. Tach!

        Ist ja auch kein Fehler, nur logisch falsch. todo_bereich.tb_code kann einen bestimmten Wert oder einen anderen Wert enthalten, aber nicht gleichzeitig zwei verschiedene.

        Ok, dann muss ich es etwas genauer erklären.

        Nein, musst du nicht, du hast das Problem nicht verstanden, es ist ein grundlegendes logisches Problem. Beim Programmieren ist die Logik von AND und OR anders als in der natürlichen Sprache.

        "Ich nehme die Schokolade und die Kekse." So sagt man, aber ein

        if (produkt = Schokolade and produkt = Kekse)

        kann nie wahr werden, weil produkt gemäß Computerlogik nicht gleichzeitig Schokolade und Kekse sein kann. Das muss zu einem OR übersetzt werden.

        if (produkt = Schokolade or produkt = Kekse)

        Der Vollständigkeit halber noch: In natürlicher Sprache wird ein "oder" üblicherweise als das verwendet, was in Computerlogik ein Exclusive-Or ist, also ein ausschließliches Oder, wie in: "Ich nehme die Schokolade oder die Kekse." Da ist ein ungesprochenes "entweder" in dem Satz enthalten.

        dedlfix.

        1. Hallo,

          kann nie wahr werden, weil produkt gemäß Computerlogik nicht gleichzeitig Schokolade und Kekse sein kann. Das muss zu einem OR übersetzt werden. if (produkt = Schokolade or produkt = Kekse)

          OR hatte ich auch schon in meiner Abfrage

          LEFT JOIN todo_bereich ON todo_bereich.tb_code = todo_grunddaten.bereichSender OR todo_bereich.tb_code = todo_grunddaten.bereichEmpfaenger
          

          allerdings erhalte ich dann bei der Ausgabe zwei Datensätze obwohl nur einer vorhanden ist.

          1. Tach!

            OR hatte ich auch schon in meiner Abfrage

            LEFT JOIN todo_bereich ON todo_bereich.tb_code = todo_grunddaten.bereichSender OR todo_bereich.tb_code = todo_grunddaten.bereichEmpfaenger
            

            allerdings erhalte ich dann bei der Ausgabe zwei Datensätze obwohl nur einer vorhanden ist.

            AND ist definitiv falsch. Es liegt nicht an AND versus OR sondern an einer anderen Ursache.

            Gemäß deiner Beispieldaten passt der Datensatz 11 auf die eine Bedingung, der 13er auf die andere, sowie der 12er auf keine. Ergibt zwei Datensätze.

            Vielleicht möchtest du ja eigentlich gar keine Veknüpfung. Versuch dein Vorhaben mal mit einer Correlated Subquery umzusetzen. Die sind meist einfacher zu verstehen als die Auswirkungen von Joins.

            Man schreibt dazu für gewünschten Wert aus der zweiten Tabelle eine Subquery in die SELECT-Klausel der ersten. Wenn du mehrere Werte aus der zweiten haben möchtest, formuliere trotzdem erstmal die Correlated Subquery, dann aber nur mit der ID oder ähnlichem signifikanten Wert aus der zweiten Tabelle. Wenn du dann die Verknüpfungslogik soweit richtig hast, kannst du immer noch auf einen Join umstellen.

            Beispiel einer Correlated Subquery:

            SELECT a.felder, (SELECT feld FROM b WHERE id = a.b_id) FROM a
            

            dedlfix.

            1. Hallo,

              deshalb wollte ich es vorhin genauer erklären wo du dann gesagt hast brauchst du nicht. Ich habe ein Datensatz wie du im ersten Bild sehen kannst

              Dort gibt es zwei Spalten einmal bereichSender und bereichEmfpaenger. Beide haben einen Wert. Dieser Wert findet man wieder in meiner zweiten Tabelle

              So, in der Ausgabe möchte ich jetzt die Zahlen in den oben genannten Spalten durch den Titel in diesem Fall I OFF und I PER ersetzten.

              in diesem Fall soll bei bereichSender = I OFF stehen und bei bereichEmpfaenger = I PER

              1. Tach!

                deshalb wollte ich es vorhin genauer erklären wo du dann gesagt hast brauchst du nicht.

                Zum Verständnis des AND/OR-Problems braucht es die Erklärung auch nicht. Aber um eine Lösung für dein eigentliches Problem zu finden, braucht es sie dann doch.

                in diesem Fall soll bei bereichSender = I OFF stehen und bei bereichEmpfaenger = I PER

                Siehe Rolf B, der hat dir die Lösung genannt/vorweggenommen. Und ja, ich bevorzuge die Subquery-Lösung, weil die kompakt in einem Subquery steht (oder zwei bei dir) und nicht quer über das Haupt-Statement verteilt ist.

                dedlfix.

        2. kann nie wahr werden, weil produkt gemäß Computerlogik nicht gleichzeitig Schokolade und Kekse sein kann.

          https://www.milka.de/produkte/milka-gro%C3%9Ftafeln/milka-schoko-,-a-,-keks?p=152&provider=%7BD193998A-4A6D-4EA5-BAA8-209357B27A09%7D&categoryId=1396 SCNR

      2. Hallo Bernd,

        du möchtest also, ausgehend von der Grunddaten-Tabelle, den Bereichsnamen des Senders und den Bereichsnamen des Empfängers haben. Diese stehen in zwei verschiedenen Rows der todo_bereich Tabelle. Deswegen musst Du zwei Zugriffe auf die todo_bereich Tabelle machen.

        Du nennst keine Spaltennamen, ich verwende mal symbolisch bereich_name für dieses Dings rechts von der ID in todo_bereich.

        Ich mache sowas mit zwei Joins, und zur Unterscheidung nehme ich Table Aliase.

        SELECT s.bereich_name as Sender, e.bereich_name as Empfaenger
        FROM todo_grunddaten g
             LEFT JOIN todo_bereich s ON g.bereichSender = s.tb_code
             LEFT JOIN todo_bereich e ON g.bereichEmpfaenger = e.tb_code
        

        Mr. Subselect, a.k.a. dedlfix, würde es so machen:

        SELECT (SELECT bereich_name FROM todo_bereich WHERE tb_code = bereichSender) as Sender,
               (SELECT bereich_name FROM todo_bereich WHERE tb_code = bereichEmfpaenger) as Empfaenger
        FROM todo_grunddaten
        

        Rolf

        Edit: Verdammt, Dedlfix war 3 Minuten früher dran 😂

        --
        sumpsi - posui - clusi
        1. Tach!

          Ergänzende Erklärung:

          du möchtest also, ausgehend von der Grunddaten-Tabelle, den Bereichsnamen des Senders und den Bereichsnamen des Empfängers haben. Diese stehen in zwei verschiedenen Rows der todo_bereich Tabelle.

          Also, wenn er das so haben möchte, dann geht das nicht mit einer einzigen Verknüpfung. Datensätze können immer nur einer der a-Tabelle mit einem anderen der b-Tabelle verknüpft werden. Die gewünschten Daten stehen aber in zwei verschiedenen Datensätzen der b-Tabelle. Deswegen braucht man die zwei Verknüpfungen. Die eine holt den einen Datensatz des Senders und die andere den anderen des Empfängers.

          dedlfix.

          1. Hallo,

            Also, wenn er das so haben möchte, dann geht das nicht mit einer einzigen Verknüpfung.

            genau das möchte ich erreichen und versuch dieses schon seit mehreren Postings zu erklären. Aber leider wohl nicht vollständig so dass man es verstanden hat. Sorry!

        2. Hallo,

          Ich mache sowas mit zwei Joins, und zur Unterscheidung nehme ich Table Aliase.

          SELECT s.bereich_name as Sender, e.bereich_name as Empfaenger
          FROM todo_grunddaten g
               LEFT JOIN todo_bereich s ON g.bereichSender = s.tb_code
               LEFT JOIN todo_bereich e ON g.bereichEmpfaenger = e.tb_code
          

          verstehe ich ehrlich gesagt nicht so recht. Woher kommen z.B. diese "s.bereich_name as Sender, e.bereich_name as Empfaenger" Werte? Ich habe es mal in meine Funktion eingesetzt

          function ToDoUebersicht($mysqli) {
              
                  $stmt = $mysqli->prepare("SELECT id, code, kurzbeschreibung, art, erstellungsdatum, job, bereichSender, apSender, bereichEmpfaenger, apEmpfaenger, prio, status,
                                                   fertigstellung_sender, fertigstellung_uhrzeit_sender, fertigstellung_empfaenger, fertigstellung_uhrzeit_empfaenger, beschreibung,
                                                   ta_titel, tp_titel, ts_titel, tb_titelkurz, s.bereich_name as Sender, e.bereich_name as Empfaenger
                                            
                                            FROM todo_grunddaten
                                           
                                            LEFT JOIN todo_art ON todo_art.ta_code = todo_grunddaten.art
                                            LEFT JOIN todo_prio ON todo_prio.tp_code = todo_grunddaten.prio
                                            LEFT JOIN todo_status ON todo_status.ts_code = todo_grunddaten.status
                                            LEFT JOIN todo_bereich s ON g.bereichSender = s.tb_code
                                            LEFT JOIN todo_bereich e ON g.bereichEmpfaenger = e.tb_code");
                 
                  $stmt->execute();
                  $stmt->bind_result($id, $code, $kurzbeschreibung, $art, $erstellungsdatum, $job, $bereichSender, $apSender, $bereichEmpfaenger, $apEmpfaenger, $prio, $status, 
                                     $fertigstellung_sender, $fertigstellung_uhrzeit_sender, $fertigstellung_empfaenger, $fertigstellung_uhrzeit_empfaenger, $beschreibung, $ta_titel, $tp_titel, 
                                     $ts_titel, $tb_titelkurz, $Sender, $Empfaenger);
                  
                  $stmt->store_result();
                  
                  if($stmt->num_rows() >  0) {     
                      while ($stmt->fetch()){
                          
                          $ToDoUebersicht[] = array( 
                              
                              'id'                                => $id, 
                              'code'                              => $code,
                              'kurzbeschreibung'                  => $kurzbeschreibung,
                              'art'                               => $art,
                              'erstellungsdatum'                  => $erstellungsdatum, 
                              'job'                               => $job,
                              'bereichSender'                     => $bereichSender,
                              'apSender'                          => $apSender,
                              'bereichEmpfaenger'                 => $bereichEmpfaenger, 
                              'apEmpfaenger'                      => $apEmpfaenger,
                              'prio'                              => $prio,
                              'status'                            => $status,
                              'fertigstellung_sender'             => $fertigstellung_sender,
                              'fertigstellung_uhrzeit_sender'     => $fertigstellung_uhrzeit_sender, 
                              'fertigstellung_empfaenger'         => $fertigstellung_empfaenger,
                              'fertigstellung_uhrzeit_empfaenger' => $fertigstellung_uhrzeit_empfaenger,
                              'beschreibung'                      => $beschreibung,
                              'ta_titel'                          => $ta_titel,
                              'tp_titel'                          => $tp_titel,
                              'ts_titel'                          => $ts_titel,
                              'tb_titelkurz'                    => $tb_titelkurz,
                              'Sender'                          => $Sender,
                              'Empfaenger'                    => $Empfaenger
                          );
                      }
                      return $ToDoUebersicht;
                      }
              }
          

          Allerdings erhalte ich dann eine Fehlermeldung

          Fatal error: Call to a member function execute() on boolean in

          1. Tach!

            Ich mache sowas mit zwei Joins, und zur Unterscheidung nehme ich Table Aliase.

            SELECT s.bereich_name as Sender, e.bereich_name as Empfaenger
            FROM todo_grunddaten g
                 LEFT JOIN todo_bereich s ON g.bereichSender = s.tb_code
                 LEFT JOIN todo_bereich e ON g.bereichEmpfaenger = e.tb_code
            

            verstehe ich ehrlich gesagt nicht so recht. Woher kommen z.B. diese "s.bereich_name as Sender, e.bereich_name as Empfaenger" Werte? Ich habe es mal in meine Funktion eingesetzt

            s und e und auch g sind die erwähnten Aliase zu den davorstehenden Tabellennamen. Man nimmt solche Aliase, um beim mehrfachen Verknüpfen derselben Tabelle zu unterscheiden, welche der beiden Vorkommen man meint. Oder wie im Falle von g, weil man sich damit kürzer auf eine Tabelle beziehen kann.

                    $stmt = $mysqli->prepare("SELECT id, code, kurzbeschreibung, art, erstellungsdatum, job, bereichSender, apSender, bereichEmpfaenger, apEmpfaenger, prio, status,
                                                     fertigstellung_sender, fertigstellung_uhrzeit_sender, fertigstellung_empfaenger, fertigstellung_uhrzeit_empfaenger, beschreibung,
                                                     ta_titel, tp_titel, ts_titel, tb_titelkurz, s.bereich_name as Sender, e.bereich_name as Empfaenger
                                              
                                              FROM todo_grunddaten
                                             
                                              LEFT JOIN todo_art ON todo_art.ta_code = todo_grunddaten.art
                                              LEFT JOIN todo_prio ON todo_prio.tp_code = todo_grunddaten.prio
                                              LEFT JOIN todo_status ON todo_status.ts_code = todo_grunddaten.status
                                              LEFT JOIN todo_bereich s ON g.bereichSender = s.tb_code
                                              LEFT JOIN todo_bereich e ON g.bereichEmpfaenger = e.tb_code");
            

            Hilfe! Weißt du auswendig, welche Spaltennamen im SELECT zu welcher Tabelle gehören? Bist du dir sicher, dass du das in Zukunft auch noch weißt, wenn du dich mal eine Weile lang nicht mehr damit beschäftig hast? Bei solchen Monster-Joins nimm lieber Aliase oder Tabellenamen zu den Feldern, damit die Herkunft klar ist. Ich würde auch lieber die Subquerys empfehlen, weil "übersichtlich" anders geht als mit Joins.

            Allerdings erhalte ich dann eine Fehlermeldung

            Fatal error: Call to a member function execute() on boolean in

            $stmt ist dann wohl nicht das Statement-Objekt sondern ein boolean. Warum? Schau da, wo es zugewiesen wurde. So ein Prepare gibt im Fehlerfall nicht das Statement sondern einen booleaschen Wert zurück. Wenn man diese Möglichkeit nicht abfängt, gibts dann solche Folgefehler. Die eigentliche Ursache wird sein, dass das Statement fehlerhaft ist und nicht präpariert werden konnte. Die genaue Meldung gibts in $mysqli->error.

            dedlfix.

            1. Hallo,

              Ich würde auch lieber die Subquerys empfehlen, weil "übersichtlich" anders geht als mit Joins.

              ich verstehe es eben nicht, wie ich es mit mehreren SELECTs machen soll. Was muss ich da alles zusammenpacken?

              1. Tach!

                Ich würde auch lieber die Subquerys empfehlen, weil "übersichtlich" anders geht als mit Joins.

                ich verstehe es eben nicht, wie ich es mit mehreren SELECTs machen soll. Was muss ich da alles zusammenpacken?

                Ein Correlated Subselect (im SELECT-Teil) kann man nehmen, wenn man aus einer zweiten Tabelle lediglich einen einzelnen Wert haben möchte. Bei mehreren Werten müsste man je Wert eine eigene Subquery schreiben. Dann kommt man mit Joins besser, sprich mit weniger Schreibaufwand zum Ziel.

                Bei einem Join vermischt man zwei Datenmengen, stellt eine Beziehung her und fischt sich dann daraus die Ergebnisse. Bei einer Correlated Subquery läuft man stattdessen durch die Datenmenge der Hauptquery und holt gezielt zum jeweiligen Datensatz einen Wert aus einer weiteren Tabelle. So zumindest ist die Semantik, der Optimizer macht im Inneren sowieso was er will. Ich finde, dass der Correlated-Subselect-Ansatz einfacher zu verstehen ist, aber vermute, dass traditionellerweise oftmals nur der Join-Ansatz gelehrt wird.

                Soweit zum Allgemeinen. Konkret hat ja Rolf B bereits ein Beispiel für beide Ansätze gegeben. Du kannst auch beides miteinander mischen. Am besten ist es wohl, wenn man das unabhängig vom PHP-Script mal in einer Datenbankumgebung (beispielsweise phpMyAdmin oder MySQL Workbench) an einfachen Beispielen übt.

                dedlfix.

                1. Hallo,

                  ich habe es jetzt wie folgt direkt im PHPmyAdmin versucht

                  SELECT id, code, kurzbeschreibung, art, erstellungsdatum, job, bereichSender, apSender, bereichEmpfaenger, apEmpfaenger, prio, status,
                                                           fertigstellung_sender, fertigstellung_uhrzeit_sender, fertigstellung_empfaenger, fertigstellung_uhrzeit_empfaenger, beschreibung,
                                                           ta_titel, tp_titel, ts_titel, tb_titelkurz 
                                                    
                                                    FROM todo_grunddaten g
                                                   
                                                    LEFT JOIN todo_art ON todo_art.ta_code = g.art
                                                    LEFT JOIN todo_prio ON todo_prio.tp_code = g.prio
                                                    LEFT JOIN todo_status ON todo_status.ts_code = g.status
                                                    LEFT JOIN todo_bereich a ON g.bereichSender = a.tb_code
                                                    LEFT JOIN todo_bereich b ON g.bereichEmpfaenger = b.tb_code
                  

                  Als Fehlermeldung erhalte ich

                  1052 - Feld 'tb_titelkurz' in field list ist nicht eindeutig

                  Verstehe ich nicht, das Feld heißt genau so.

                  1. Hallo,

                    vielen vielen Dank für eure Hilfe und Geduld. Ich habe die Lösung

                    SELECT id, code, kurzbeschreibung, art, erstellungsdatum, job, bereichSender, apSender,
                           bereichEmpfaenger, apEmpfaenger, prio, status, fertigstellung_sender,
                           fertigstellung_uhrzeit_sender, fertigstellung_empfaenger, 
                           fertigstellung_uhrzeit_empfaenger, beschreibung, ta_titel, tp_titel, ts_titel,
                           s.tb_titelkurz as Sender, e.tb_titelkurz as Empfaenger
                                                      
                    FROM todo_grunddaten g
                                                     
                       LEFT JOIN todo_art ON todo_art.ta_code = g.art
                       LEFT JOIN todo_prio ON todo_prio.tp_code = g.prio
                       LEFT JOIN todo_status ON todo_status.ts_code = g.status
                       LEFT JOIN todo_bereich s ON g.bereichSender = s.tb_code
                       LEFT JOIN todo_bereich e ON g.bereichEmpfaenger = e.tb_code
                    
                    1. Hallo Bernd,

                      super!

                      Die gleiche Tabelle mehrfach zu Joinen ist durchaus eine Kunst für sich. Und da hast Du nun die ersten Pinselstriche getan.

                      Danke an dedlfix für den Support - ich musste mich einige Zeit mit meinem Brötchenjob befassen…

                      Rolf

                      --
                      sumpsi - posui - clusi
                      1. Hallo Rolf,

                        Danke an dedlfix für den Support - ich musste mich einige Zeit mit meinem Brötchenjob befassen…

                        der Brötchenjob geht selbstverständlich vor!

                      2. Hallo,

                        ich musste mich einige Zeit mit meinem Brötchenjob befassen…

                        ungewöhnliche Arbeitszeit für einen Bäcker…

                        Gruß
                        Kalk

                  2. Tach!

                    SELECT id, code, kurzbeschreibung, art, erstellungsdatum, job, bereichSender, apSender, bereichEmpfaenger, apEmpfaenger, prio, status,
                                                             fertigstellung_sender, fertigstellung_uhrzeit_sender, fertigstellung_empfaenger, fertigstellung_uhrzeit_empfaenger, beschreibung,
                                                             ta_titel, tp_titel, ts_titel, tb_titelkurz 
                                                      
                                                      FROM todo_grunddaten g
                                                     
                                                      LEFT JOIN todo_art ON todo_art.ta_code = g.art
                                                      LEFT JOIN todo_prio ON todo_prio.tp_code = g.prio
                                                      LEFT JOIN todo_status ON todo_status.ts_code = g.status
                                                      LEFT JOIN todo_bereich a ON g.bereichSender = a.tb_code
                                                      LEFT JOIN todo_bereich b ON g.bereichEmpfaenger = b.tb_code
                    

                    Als Fehlermeldung erhalte ich

                    1052 - Feld 'tb_titelkurz' in field list ist nicht eindeutig

                    Verstehe ich nicht, das Feld heißt genau so.

                    Du hast die Lösung ja schon gefunden, ich sag trotzdem noch ein paar Worte, z.B. falls irgendwer anders drüber stolpert. Die Eindeutigkeit bezieht sich hier nicht auf den Namen, sondern auf die Herkunft. Die ist nämlich nicht klar, wenn es mehrere Felder mit demselben Namen in der Ergebnismenge gibt. Die Dopplung kommt zustande, weil für das gewünschte Ergebnis die Tabelle todo_bereich zweimal gejoint wurde und ihre Felder damit doppelt enthalten sind. Nun muss eindeutig gekennzeichnet werden, welche Tabelle nun gemeint ist, was man durch Voranstellen des Namens oder des vergebenen Alias macht. Im Übrigen empfiehlt es sich auch für die anderen Tabellen einen Alias zu vergeben und deren Felder mit diesem Alias zu versehen. Und zwar nicht, weil es notwendig wäre - ist es ja nicht, solange keine Namensdopplungen auftreten -, sondern weil man so einfacher nachvollziehen kann, welches Feld aus welcher Tabelle kommt.

                    dedlfix.

                    1. Hi,

                      Im Übrigen empfiehlt es sich auch für die anderen Tabellen einen Alias zu vergeben und deren Felder mit diesem Alias zu versehen. Und zwar nicht, weil es notwendig wäre - ist es ja nicht, solange keine Namensdopplungen auftreten -, sondern weil man so einfacher nachvollziehen kann, welches Feld aus welcher Tabelle kommt.

                      ich mach das inzwischen sogar auch schon dann, wenn ich (noch) gar keinen join im select habe.

                      Hat den Vorteil, daß beim Erweitern des Statements um einen Join nur der neue Teil bearbeitet werden muß.

                      cu,
                      Andreas a/k/a MudGuard