W3docs

MySQL Join

MySQL-Tabellen in Python mit INNER JOIN, LEFT JOIN, RIGHT JOIN und FULL OUTER JOIN kombinieren. Mit Beispielen und Fehlerbehandlung.

Ein SQL JOIN ermöglicht es, Zeilen aus zwei oder mehr Tabellen anhand einer gemeinsamen Spalte zu verknüpfen. Diese Seite erklärt jeden Join-Typ, der beim Arbeiten mit MySQL aus Python heraus unterstützt wird, zeigt vollständige, ausführbare Beispiele für jeden Typ und behandelt Best Practices wie parametrisierte Abfragen und ordnungsgemäßes Schließen von Ressourcen.

Bevor Sie dieses Kapitel lesen, stellen Sie sicher, dass Sie mit dem Verbinden mit MySQL, dem Erstellen von Tabellen und dem Auswählen von Zeilen vertraut sind.

Voraussetzungen

Installieren Sie den Connector, falls noch nicht geschehen:

pip install mysql-connector-python

Beispieltabellen in diesem Kapitel

Alle nachfolgenden Beispiele setzen voraus, dass zwei Tabellen — customers und orders — in einer Datenbank namens mydatabase vorhanden sind. Führen Sie dieses SQL einmalig aus, um sie zu erstellen und zu befüllen:

CREATE TABLE IF NOT EXISTS customers (
  id      INT AUTO_INCREMENT PRIMARY KEY,
  name    VARCHAR(100) NOT NULL,
  address VARCHAR(200)
);

CREATE TABLE IF NOT EXISTS orders (
  id           INT AUTO_INCREMENT PRIMARY KEY,
  customer_id  INT,
  order_date   DATE,
  order_total  DECIMAL(10, 2),
  FOREIGN KEY (customer_id) REFERENCES customers(id)
);

INSERT INTO customers (name, address) VALUES
  ('Alice',   '123 Maple St'),
  ('Bob',     '456 Oak Ave'),
  ('Charlie', '789 Pine Rd');

INSERT INTO orders (customer_id, order_date, order_total) VALUES
  (1, '2024-01-10', 99.99),
  (1, '2024-02-14', 45.00),
  (2, '2024-03-05', 210.50);
-- Charlie has no orders, so he will appear only in LEFT/FULL joins.

Beachten Sie, dass Charlie keine zugehörigen Bestellungen hat. Dieses Detail macht den Unterschied zwischen den Join-Typen in der Ausgabe deutlich sichtbar.

Arten von Tabellenverknüpfungen

Join-TypWas er zurückgibt
INNER JOINNur Zeilen, die in beiden Tabellen übereinstimmen
LEFT JOINAlle Zeilen aus der linken Tabelle; NULL, wo es rechts keine Übereinstimmung gibt
RIGHT JOINAlle Zeilen aus der rechten Tabelle; NULL, wo es links keine Übereinstimmung gibt
FULL OUTER JOIN (via UNION)Alle Zeilen aus beiden Tabellen; NULL auf der Seite ohne Übereinstimmung

MySQL besitzt kein FULL OUTER JOIN-Schlüsselwort. Verwenden Sie ein UNION aus einem LEFT JOIN und einem RIGHT JOIN, um dasselbe Ergebnis zu erzielen.

INNER JOIN

Ein INNER JOIN gibt nur die Zeilen zurück, bei denen die Join-Bedingung in beiden Tabellen erfüllt ist. Verwenden Sie ihn, wenn Sie nur Kunden interessieren, die tatsächlich Bestellungen haben.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Erwartete Ausgabe (mit den obigen Beispieldaten):

('Alice', '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice', '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',   '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))

Charlie fehlt, weil er keine übereinstimmenden Bestellzeilen hat.

LEFT JOIN

Ein LEFT JOIN gibt jede Zeile aus der linken Tabelle (customers) und die übereinstimmenden Zeilen aus der rechten Tabelle (orders) zurück. Gibt es keine Übereinstimmung, sind die Spalten der rechten Tabelle in Python None.

Verwenden Sie einen LEFT JOIN, wenn Sie alle Kunden sehen möchten, auch solche, die noch keine Bestellungen aufgegeben haben.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        LEFT JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Erwartete Ausgabe:

('Alice',   '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice',   '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',     '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))
('Charlie', '789 Pine Rd',  None,                       None)

Charlie erscheint mit None für die Bestellspalten, da er keine Bestellungen hat.

RIGHT JOIN

Ein RIGHT JOIN ist das Spiegelbild eines LEFT JOIN. Er gibt jede Zeile aus der rechten Tabelle (orders) und die übereinstimmenden Zeilen aus der linken Tabelle (customers) zurück. Zeilen in orders, die keinen passenden Kunden haben, zeigen None für die Kundenspalten.

In der Praxis ist RIGHT JOIN weniger verbreitet als LEFT JOIN, da er sich immer durch Vertauschen der Tabellenreihenfolge als LEFT JOIN umschreiben lässt.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        RIGHT JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Erwartete Ausgabe (mit den Beispieldaten haben alle Bestellungen einen passenden Kunden, daher sieht das Ergebnis genauso aus wie bei INNER JOIN):

('Alice', '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice', '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',   '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))

FULL OUTER JOIN (via UNION)

MySQL besitzt kein FULL OUTER JOIN-Schlüsselwort, aber Sie können dasselbe Ergebnis erzielen, indem Sie einen LEFT JOIN und einen RIGHT JOIN mit UNION kombinieren. UNION entfernt dabei automatisch doppelte Zeilen.

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        LEFT JOIN orders ON customers.id = orders.customer_id

        UNION

        SELECT customers.name, customers.address,
               orders.order_date, orders.order_total
        FROM customers
        RIGHT JOIN orders ON customers.id = orders.customer_id
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Erwartete Ausgabe:

('Alice',   '123 Maple St', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice',   '123 Maple St', datetime.date(2024, 2, 14), Decimal('45.00'))
('Bob',     '456 Oak Ave',  datetime.date(2024, 3,  5), Decimal('210.50'))
('Charlie', '789 Pine Rd',  None,                       None)

Alle Kunden erscheinen (einschließlich Charlie ohne Bestellungen) und alle Bestellungen erscheinen (einschließlich solcher, die möglicherweise keinen passenden Kunden haben).

JOIN-Ergebnisse mit WHERE filtern

Sie können einer beliebigen Verknüpfung eine WHERE-Klausel hinzufügen, um die Ergebnismenge einzuschränken. Verwenden Sie immer parametrisierte Abfragen (den %s-Platzhalter) anstelle von String-Formatierung, um SQL-Injection zu vermeiden.

Das folgende Beispiel ruft nur die Bestellungen für einen bestimmten Kunden anhand des Namens ab:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
        WHERE customers.name = %s
    """
    val = ("Alice",)
    mycursor.execute(sql, val)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Erwartete Ausgabe:

('Alice', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))

JOIN-Ergebnisse mit ORDER BY sortieren

Kombinieren Sie eine Verknüpfung mit ORDER BY, um die Ausgabereihenfolge zu steuern. Dieses Beispiel listet alle Kundenbestellungen, sortiert nach der neuesten Bestellung zuerst:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
        ORDER BY orders.order_date DESC
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Erwartete Ausgabe:

('Bob',   datetime.date(2024, 3,  5), Decimal('210.50'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))
('Alice', datetime.date(2024, 1, 10), Decimal('99.99'))

JOIN-Ergebnisse mit LIMIT begrenzen

Kombinieren Sie eine Verknüpfung mit einer LIMIT-Klausel, um effizient durch große Ergebnismengen zu blättern:

import mysql.connector
from mysql.connector import Error

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="mydatabase"
    )
    mycursor = mydb.cursor()

    sql = """
        SELECT customers.name, orders.order_date, orders.order_total
        FROM customers
        INNER JOIN orders ON customers.id = orders.customer_id
        ORDER BY orders.order_date DESC
        LIMIT 2
    """
    mycursor.execute(sql)
    results = mycursor.fetchall()

    for row in results:
        print(row)

except Error as e:
    print(f"Error: {e}")
finally:
    if mycursor:
        mycursor.close()
    if mydb.is_connected():
        mydb.close()

Erwartete Ausgabe (nur die zwei neuesten Bestellungen):

('Bob',   datetime.date(2024, 3,  5), Decimal('210.50'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))

Best Practices

  • Verwenden Sie parametrisierte Abfragen. Übergeben Sie vom Benutzer stammende Werte als zweites Argument an cursor.execute() mit %s-Platzhaltern. Verwenden Sie niemals Python-String-Formatierung oder f-Strings zum Erstellen von SQL — das öffnet Ihrer Anwendung Tür und Tor für SQL-Injection.
  • Kapseln Sie Datenbankcode in try...except...finally. Das stellt sicher, dass Verbindungen und Cursor immer geschlossen werden, auch wenn ein Fehler auftritt.
  • Wählen Sie nur die benötigten Spalten aus. Die Verwendung von SELECT * über verknüpfte Tabellen kann viele redundante Spalten einlesen und bei großen Tabellen die Leistung beeinträchtigen.
  • Fügen Sie Indizes für Join-Spalten hinzu. Wenn orders.customer_id nicht indiziert ist, scannt MySQL bei jedem Join die gesamte Tabelle. Eine Fremdschlüssel-Bedingung (wie im obigen Setup-Skript gezeigt) erstellt automatisch einen Index.
  • Bevorzugen Sie LEFT JOIN gegenüber RIGHT JOIN für bessere Lesbarkeit. Ein RIGHT JOIN lässt sich immer als LEFT JOIN umschreiben, indem die Tabellenpositionen getauscht werden — das ist für die meisten Entwickler leichter nachzuvollziehen.

Kurzübersicht

SzenarioZu verwendender Join
Nur Datensätze mit Übereinstimmungen in beiden TabellenINNER JOIN
Alle Datensätze aus der primären (linken) Tabelle, mit oder ohne ÜbereinstimmungLEFT JOIN
Alle Datensätze aus der sekundären (rechten) Tabelle, mit oder ohne ÜbereinstimmungRIGHT JOIN
Alle Datensätze aus beiden Tabellen, mit oder ohne ÜbereinstimmungLEFT JOIN ... UNION ... RIGHT JOIN
Verknüpfte Zeilen einschränkenWHERE-Klausel mit parametrisierten Werten hinzufügen
Ausgabereihenfolge steuernORDER BY column ASC|DESC hinzufügen
Ergebnisse seitenweise anzeigenLIMIT n hinzufügen (siehe MySQL Limit)
Was this page helpful?