MySQL Join
Aprende a combinar tablas MySQL en Python con INNER JOIN, LEFT JOIN, RIGHT JOIN y FULL OUTER JOIN. Incluye ejemplos con manejo de errores.
Un JOIN de SQL permite combinar filas de dos o más tablas basándose en una columna relacionada. Esta página explica cada tipo de join compatible al trabajar con MySQL desde Python, muestra ejemplos completos y ejecutables para cada uno, y cubre las mejores prácticas como las consultas parametrizadas y la limpieza adecuada de recursos.
Antes de leer este capítulo, asegúrate de estar familiarizado con la conexión a MySQL, la creación de tablas y la selección de filas.
Requisitos previos
Instala el conector si aún no lo has hecho:
pip install mysql-connector-pythonTablas de ejemplo usadas en este capítulo
Todos los ejemplos a continuación asumen que existen dos tablas — customers y orders — en una base de datos llamada mydatabase. Ejecuta este SQL una vez para crearlas y poblarlas:
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.Observa que Charlie no tiene pedidos. Este detalle hace que la diferencia entre los tipos de join sea evidente en la salida.
Tipos de joins de tablas
| Tipo de join | Qué devuelve |
|---|---|
INNER JOIN | Solo las filas que coinciden en ambas tablas |
LEFT JOIN | Todas las filas de la tabla izquierda; NULL donde no hay coincidencia en la derecha |
RIGHT JOIN | Todas las filas de la tabla derecha; NULL donde no hay coincidencia en la izquierda |
FULL OUTER JOIN (mediante UNION) | Todas las filas de ambas tablas; NULL en el lado que no tiene coincidencia |
MySQL no tiene la palabra clave FULL OUTER JOIN. Usa una UNION de un LEFT JOIN y un RIGHT JOIN para obtener el mismo resultado.
INNER JOIN
Un INNER JOIN devuelve solo las filas donde se cumple la condición de join en ambas tablas. Úsalo cuando solo te interesan los clientes que realmente tienen pedidos.
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()Salida esperada (usando los datos de ejemplo anteriores):
('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 no aparece porque no tiene filas de pedido coincidentes.
LEFT JOIN
Un LEFT JOIN devuelve cada fila de la tabla izquierda (customers) y las filas coincidentes de la tabla derecha (orders). Cuando no hay coincidencia, las columnas de la tabla derecha son None en Python.
Usa un LEFT JOIN cuando quieras ver todos los clientes, incluso los que aún no han realizado ningún pedido.
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()Salida esperada:
('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 aparece con None en las columnas de pedido porque no tiene pedidos.
RIGHT JOIN
Un RIGHT JOIN es la imagen especular de un LEFT JOIN. Devuelve cada fila de la tabla derecha (orders) y las filas coincidentes de la tabla izquierda (customers). Las filas en orders que no tienen un cliente coincidente muestran None en las columnas de cliente.
En la práctica, RIGHT JOIN es menos común que LEFT JOIN porque siempre se puede reescribir como un LEFT JOIN intercambiando el orden de las tablas.
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()Salida esperada (con los datos de ejemplo, todos los pedidos tienen un cliente coincidente, por lo que el resultado es igual que con 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 (mediante UNION)
MySQL no tiene la palabra clave FULL OUTER JOIN, pero puedes obtener el mismo resultado combinando un LEFT JOIN y un RIGHT JOIN con UNION. UNION elimina automáticamente las filas duplicadas.
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()Salida esperada:
('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)Todos los clientes aparecen (incluido Charlie sin pedidos) y todos los pedidos aparecen (incluidos los que podrían no tener cliente coincidente).
Filtrar resultados de JOIN con WHERE
Puedes agregar una cláusula WHERE a cualquier join para reducir el conjunto de resultados. Usa siempre consultas parametrizadas (el marcador de posición %s) en lugar de formateo de cadenas para evitar la inyección SQL.
El siguiente ejemplo recupera solo los pedidos de un cliente específico por nombre:
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()Salida esperada:
('Alice', datetime.date(2024, 1, 10), Decimal('99.99'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))Ordenar resultados de JOIN con ORDER BY
Combina un join con ORDER BY para controlar el orden de salida. Este ejemplo lista todos los pedidos de clientes ordenados por el más reciente primero:
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()Salida esperada:
('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'))Limitar resultados de JOIN con LIMIT
Combina un join con una cláusula LIMIT para paginar grandes conjuntos de resultados de forma eficiente:
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()Salida esperada (solo los dos pedidos más recientes):
('Bob', datetime.date(2024, 3, 5), Decimal('210.50'))
('Alice', datetime.date(2024, 2, 14), Decimal('45.00'))Mejores prácticas
- Usa consultas parametrizadas. Pasa los valores suministrados por el usuario como segundo argumento a
cursor.execute()con marcadores de posición%s. Nunca uses el formateo de cadenas de Python ni f-strings para construir SQL — eso expone tu aplicación a la inyección SQL. - Envuelve el código de base de datos en
try...except...finally. Esto garantiza que las conexiones y los cursores siempre se cierren, incluso cuando ocurre un error. - Selecciona solo las columnas que necesitas. Usar
SELECT *a través de tablas unidas puede traer muchas columnas redundantes y perjudicar el rendimiento en tablas grandes. - Agrega índices en las columnas de join. Si
orders.customer_idno está indexado, MySQL escaneará toda la tabla para cada join. Una restricción de clave foránea (como se muestra en el script de configuración anterior) crea un índice automáticamente. - Prefiere
LEFT JOINsobreRIGHT JOINpara mayor legibilidad. UnRIGHT JOINsiempre se puede reescribir como unLEFT JOINintercambiando las posiciones de las tablas, lo que la mayoría de los desarrolladores encuentra más fácil de seguir.
Referencia rápida
| Escenario | Join a usar |
|---|---|
| Solo registros con coincidencias en ambas tablas | INNER JOIN |
| Todos los registros de la tabla principal (izquierda), con o sin coincidencia | LEFT JOIN |
| Todos los registros de la tabla secundaria (derecha), con o sin coincidencia | RIGHT JOIN |
| Todos los registros de ambas tablas, con o sin coincidencia | LEFT JOIN ... UNION ... RIGHT JOIN |
| Reducir las filas unidas | Agregar una cláusula WHERE con valores parametrizados |
| Controlar el orden de salida | Agregar ORDER BY column ASC|DESC |
| Paginar resultados | Agregar LIMIT n (ver MySQL Limit) |