W3docs

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-python

Tablas 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 joinQué devuelve
INNER JOINSolo las filas que coinciden en ambas tablas
LEFT JOINTodas las filas de la tabla izquierda; NULL donde no hay coincidencia en la derecha
RIGHT JOINTodas 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_id no 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 JOIN sobre RIGHT JOIN para mayor legibilidad. Un RIGHT JOIN siempre se puede reescribir como un LEFT JOIN intercambiando las posiciones de las tablas, lo que la mayoría de los desarrolladores encuentra más fácil de seguir.

Referencia rápida

EscenarioJoin a usar
Solo registros con coincidencias en ambas tablasINNER JOIN
Todos los registros de la tabla principal (izquierda), con o sin coincidenciaLEFT JOIN
Todos los registros de la tabla secundaria (derecha), con o sin coincidenciaRIGHT JOIN
Todos los registros de ambas tablas, con o sin coincidenciaLEFT JOIN ... UNION ... RIGHT JOIN
Reducir las filas unidasAgregar una cláusula WHERE con valores parametrizados
Controlar el orden de salidaAgregar ORDER BY column ASC|DESC
Paginar resultadosAgregar LIMIT n (ver MySQL Limit)
Was this page helpful?