W3docs

MySQL Join

Aprenda a combinar tabelas MySQL em Python com INNER JOIN, LEFT JOIN, RIGHT JOIN e FULL OUTER JOIN. Inclui exemplos com tratamento de erros.

Um JOIN SQL permite combinar linhas de duas ou mais tabelas com base em uma coluna relacionada. Esta página explica cada tipo de join suportado ao trabalhar com MySQL a partir do Python, apresenta exemplos completos e executáveis para cada um, e aborda boas práticas como consultas parametrizadas e limpeza adequada de recursos.

Antes de ler este capítulo, certifique-se de que você está confortável com a conexão ao MySQL, a criação de tabelas e a seleção de linhas.

Pré-requisitos

Instale o conector, caso ainda não tenha feito:

pip install mysql-connector-python

Tabelas de exemplo usadas neste capítulo

Todos os exemplos abaixo pressupõem que duas tabelas — customers e orders — existam em um banco de dados chamado mydatabase. Execute este SQL uma vez para criá-las e preenchê-las:

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.

Note que Charlie não possui pedidos correspondentes. Esse detalhe torna a diferença entre os tipos de join óbvia na saída.

Tipos de joins de tabelas

Tipo de joinO que retorna
INNER JOINApenas as linhas que correspondem em ambas as tabelas
LEFT JOINTodas as linhas da tabela da esquerda; NULL onde não há correspondência à direita
RIGHT JOINTodas as linhas da tabela da direita; NULL onde não há correspondência à esquerda
FULL OUTER JOIN (via UNION)Todas as linhas de ambas as tabelas; NULL no lado que não tiver correspondência

O MySQL não possui a palavra-chave FULL OUTER JOIN. Use um UNION de um LEFT JOIN e um RIGHT JOIN para obter o mesmo resultado.

INNER JOIN

Um INNER JOIN retorna apenas as linhas em que a condição de join é satisfeita em ambas as tabelas. Use-o quando você só se preocupa com clientes que realmente possuem 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()

Saída esperada (usando os dados de exemplo acima):

('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 está ausente porque não possui linhas de pedido correspondentes.

LEFT JOIN

Um LEFT JOIN retorna todas as linhas da tabela da esquerda (customers) e as linhas correspondentes da tabela da direita (orders). Quando não há correspondência, as colunas da tabela da direita são None em Python.

Use um LEFT JOIN quando quiser ver todos os clientes, mesmo os que ainda não fizeram 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
        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()

Saída 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 com None nas colunas de pedido porque não possui pedidos.

RIGHT JOIN

Um RIGHT JOIN é a imagem espelhada de um LEFT JOIN. Ele retorna todas as linhas da tabela da direita (orders) e as linhas correspondentes da tabela da esquerda (customers). As linhas em orders que não possuem um cliente correspondente mostram None nas colunas do cliente.

Na prática, o RIGHT JOIN é menos comum que o LEFT JOIN, pois sempre é possível reescrevê-lo como um LEFT JOIN trocando a ordem das tabelas.

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()

Saída esperada (com os dados de exemplo, todos os pedidos possuem um cliente correspondente, portanto o resultado é igual ao do 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)

O MySQL não possui a palavra-chave FULL OUTER JOIN, mas você pode obter o mesmo resultado combinando um LEFT JOIN e um RIGHT JOIN com UNION. O UNION remove automaticamente as linhas 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()

Saída 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 os clientes aparecem (incluindo Charlie sem pedidos) e todos os pedidos aparecem (incluindo aqueles que porventura não tenham um cliente correspondente).

Filtrando resultados de JOIN com WHERE

Você pode adicionar uma cláusula WHERE a qualquer join para restringir o conjunto de resultados. Sempre use consultas parametrizadas (o marcador %s) em vez de formatação de strings para evitar injeção de SQL.

O exemplo a seguir recupera apenas os pedidos de um cliente específico pelo nome:

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()

Saída esperada:

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

Ordenando resultados de JOIN com ORDER BY

Combine um join com ORDER BY para controlar a ordem da saída. Este exemplo lista todos os pedidos de clientes ordenados pelo pedido mais recente primeiro:

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()

Saída 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'))

Limitando resultados de JOIN com LIMIT

Combine um join com uma 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()

Saída esperada (apenas os dois pedidos mais recentes):

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

Boas práticas

  • Use consultas parametrizadas. Passe valores fornecidos pelo usuário como segundo argumento para cursor.execute() com marcadores %s. Nunca use formatação de strings Python ou f-strings para construir SQL — isso expõe sua aplicação a injeção de SQL.
  • Envolva o código de banco de dados em try...except...finally. Isso garante que conexões e cursores sejam sempre fechados, mesmo quando ocorre um erro.
  • Selecione apenas as colunas de que você precisa. Usar SELECT * em tabelas unidas pode trazer muitas colunas redundantes e prejudicar o desempenho em tabelas grandes.
  • Adicione índices nas colunas de join. Se orders.customer_id não estiver indexado, o MySQL fará uma varredura completa da tabela a cada join. Uma restrição de chave estrangeira (como mostrado no script de configuração acima) cria um índice automaticamente.
  • Prefira LEFT JOIN em vez de RIGHT JOIN para maior legibilidade. Um RIGHT JOIN sempre pode ser reescrito como um LEFT JOIN trocando as posições das tabelas, o que a maioria dos desenvolvedores acha mais fácil de entender.

Referência rápida

CenárioJoin a usar
Apenas registros com correspondências em ambas as tabelasINNER JOIN
Todos os registros da tabela principal (esquerda), com ou sem correspondênciaLEFT JOIN
Todos os registros da tabela secundária (direita), com ou sem correspondênciaRIGHT JOIN
Todos os registros de ambas as tabelas, com ou sem correspondênciaLEFT JOIN ... UNION ... RIGHT JOIN
Restringir as linhas unidasAdicione uma cláusula WHERE com valores parametrizados
Controlar a ordem da saídaAdicione ORDER BY column ASC|DESC
Paginar resultadosAdicione LIMIT n (veja MySQL Limit)
Was this page helpful?