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-pythonBeispieltabellen 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-Typ | Was er zurückgibt |
|---|---|
INNER JOIN | Nur Zeilen, die in beiden Tabellen übereinstimmen |
LEFT JOIN | Alle Zeilen aus der linken Tabelle; NULL, wo es rechts keine Übereinstimmung gibt |
RIGHT JOIN | Alle 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_idnicht 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 JOINgegenüberRIGHT JOINfür bessere Lesbarkeit. EinRIGHT JOINlässt sich immer alsLEFT JOINumschreiben, indem die Tabellenpositionen getauscht werden — das ist für die meisten Entwickler leichter nachzuvollziehen.
Kurzübersicht
| Szenario | Zu verwendender Join |
|---|---|
| Nur Datensätze mit Übereinstimmungen in beiden Tabellen | INNER JOIN |
| Alle Datensätze aus der primären (linken) Tabelle, mit oder ohne Übereinstimmung | LEFT JOIN |
| Alle Datensätze aus der sekundären (rechten) Tabelle, mit oder ohne Übereinstimmung | RIGHT JOIN |
| Alle Datensätze aus beiden Tabellen, mit oder ohne Übereinstimmung | LEFT JOIN ... UNION ... RIGHT JOIN |
| Verknüpfte Zeilen einschränken | WHERE-Klausel mit parametrisierten Werten hinzufügen |
| Ausgabereihenfolge steuern | ORDER BY column ASC|DESC hinzufügen |
| Ergebnisse seitenweise anzeigen | LIMIT n hinzufügen (siehe MySQL Limit) |