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-pythonTabelas 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 join | O que retorna |
|---|---|
INNER JOIN | Apenas as linhas que correspondem em ambas as tabelas |
LEFT JOIN | Todas as linhas da tabela da esquerda; NULL onde não há correspondência à direita |
RIGHT JOIN | Todas 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_idnã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 JOINem vez deRIGHT JOINpara maior legibilidade. UmRIGHT JOINsempre pode ser reescrito como umLEFT JOINtrocando as posições das tabelas, o que a maioria dos desenvolvedores acha mais fácil de entender.
Referência rápida
| Cenário | Join a usar |
|---|---|
| Apenas registros com correspondências em ambas as tabelas | INNER JOIN |
| Todos os registros da tabela principal (esquerda), com ou sem correspondência | LEFT JOIN |
| Todos os registros da tabela secundária (direita), com ou sem correspondência | RIGHT JOIN |
| Todos os registros de ambas as tabelas, com ou sem correspondência | LEFT JOIN ... UNION ... RIGHT JOIN |
| Restringir as linhas unidas | Adicione uma cláusula WHERE com valores parametrizados |
| Controlar a ordem da saída | Adicione ORDER BY column ASC|DESC |
| Paginar resultados | Adicione LIMIT n (veja MySQL Limit) |