SQLAlchemy Core: consultas sem ORM
1. Introdução ao SQLAlchemy Core
SQLAlchemy é um dos frameworks de banco de dados mais poderosos do ecossistema Python, oferecendo duas abordagens distintas para interagir com bancos de dados relacionais: o ORM (Object-Relational Mapping) e o Core. Enquanto o ORM mapeia tabelas para classes Python e linhas para objetos, o Core trabalha diretamente com construções SQL em nível de schema e expressão.
O Core é ideal quando você precisa de:
- Controle fino sobre as consultas SQL geradas
- Performance máxima sem overhead de objetos
- Operações bulk e batch processing
- Integração com sistemas legados ou esquemas complexos
Para instalar o SQLAlchemy:
pip install sqlalchemy
2. Configuração do Engine e Conexão
O Engine é o ponto de partida para qualquer operação com SQLAlchemy Core. Ele gerencia o pool de conexões e dialoga com o banco de dados específico.
from sqlalchemy import create_engine
# Engine para SQLite (arquivo local)
engine = create_engine('sqlite:///meu_banco.db', echo=True)
# Engine para PostgreSQL
# engine = create_engine('postgresql://usuario:senha@localhost:5432/meudb')
# Usando context manager para gerenciar conexões
with engine.connect() as conn:
resultado = conn.execute(text("SELECT 1"))
print(resultado.fetchone())
O parâmetro echo=True ativa o logging das queries SQL geradas, essencial para depuração. O pooling padrão do SQLAlchemy gerencia automaticamente o reaproveitamento de conexões.
3. Definição de Esquemas com Metadata e Table
No Core, definimos tabelas como objetos Table associados a um objeto MetaData. Isso nos dá controle total sobre tipos, constraints e relacionamentos.
from sqlalchemy import MetaData, Table, Column, Integer, String, Float, DateTime
from datetime import datetime
metadata = MetaData()
# Definindo tabela de produtos
produtos = Table(
'produtos',
metadata,
Column('id', Integer, primary_key=True, autoincrement=True),
Column('nome', String(100), nullable=False),
Column('preco', Float, nullable=False),
Column('categoria', String(50)),
Column('criado_em', DateTime, default=datetime.utcnow)
)
# Criando as tabelas no banco
metadata.create_all(engine)
Para refletir tabelas existentes do banco:
from sqlalchemy import inspect
inspector = inspect(engine)
tabelas = inspector.get_table_names()
print(f"Tabelas existentes: {tabelas}")
# Refletir uma tabela específica
from sqlalchemy import Table
usuarios = Table('usuarios', metadata, autoload_with=engine)
4. Construção de Consultas com select()
O Core constrói consultas usando objetos select() que podem ser combinados e modificados programaticamente.
from sqlalchemy import select, and_, or_
# Consulta básica
query = select(produtos)
resultado = conn.execute(query)
# Filtragem com where
query = select(produtos).where(produtos.c.preco > 100.0)
# Combinação de condições
query = select(produtos).where(
and_(
produtos.c.preco.between(50, 200),
or_(
produtos.c.categoria == 'Eletrônicos',
produtos.c.categoria == 'Informática'
)
)
)
# Ordenação e paginação
query = select(produtos).order_by(
produtos.c.preco.desc()
).limit(10).offset(20)
5. Execução de Consultas e Manipulação de Resultados
A execução de consultas retorna objetos Result que oferecem múltiplos métodos para acesso aos dados.
with engine.connect() as conn:
# Executando consulta
query = select(produtos)
result = conn.execute(query)
# fetchone() - pega um registro
primeiro = result.fetchone()
print(f"ID: {primeiro.id}, Nome: {primeiro.nome}")
# fetchall() - pega todos
result = conn.execute(query)
todos = result.fetchall()
# Mapeando para dicionários
result = conn.execute(query)
for row in result.mappings():
print(dict(row))
# scalar() - pega primeiro valor da primeira coluna
query = select(produtos.c.preco).where(produtos.c.id == 1)
preco = conn.execute(query).scalar()
print(f"Preço do produto 1: {preco}")
6. Inserção, Atualização e Exclusão com Core
Operações de modificação usam objetos insert(), update() e delete().
from sqlalchemy import insert, update, delete
with engine.connect() as conn:
# Inserção
stmt = insert(produtos).values(
nome='Notebook Dell',
preco=4500.00,
categoria='Informática'
)
result = conn.execute(stmt)
print(f"ID inserido: {result.inserted_primary_key}")
# Atualização condicional
stmt = update(produtos).where(
produtos.c.categoria == 'Informática'
).values(preco=produtos.c.preco * 1.1) # 10% de aumento
conn.execute(stmt)
# Exclusão segura
stmt = delete(produtos).where(
produtos.c.preco < 10.0
)
linhas_afetadas = conn.execute(stmt).rowcount
print(f"{linhas_afetadas} produtos removidos")
conn.commit() # Confirma as alterações
7. Junções e Subconsultas Avançadas
O Core suporta operações relacionais completas com joins, subconsultas e funções agregadas.
from sqlalchemy import func, exists
# Definindo tabela adicional para vendas
vendas = Table(
'vendas',
metadata,
Column('id', Integer, primary_key=True),
Column('produto_id', Integer, nullable=False),
Column('quantidade', Integer),
Column('data_venda', DateTime)
)
metadata.create_all(engine)
# JOIN entre tabelas
query = select(
produtos.c.nome,
func.sum(vendas.c.quantidade).label('total_vendido')
).join(
vendas, produtos.c.id == vendas.c.produto_id
).group_by(produtos.c.nome)
# Subconsulta com EXISTS
subq = select(vendas.c.produto_id).where(
vendas.c.data_venda >= '2024-01-01'
)
query = select(produtos).where(
exists(subq.where(
vendas.c.produto_id == produtos.c.id
))
)
# Funções agregadas
query = select(
produtos.c.categoria,
func.count().label('quantidade'),
func.avg(produtos.c.preco).label('preco_medio'),
func.sum(produtos.c.preco).label('valor_total')
).group_by(produtos.c.categoria)
8. Transações e Boas Práticas
Gerenciamento explícito de transações é crucial para consistência dos dados.
from sqlalchemy import text
# Transação manual
with engine.connect() as conn:
trans = conn.begin()
try:
# Operações dentro da transação
conn.execute(
insert(produtos).values(nome='Mouse', preco=89.90)
)
# Savepoint para rollback parcial
savepoint = conn.begin_nested()
try:
conn.execute(
insert(produtos).values(nome='Teclado', preco=-50.00)
)
except Exception:
savepoint.rollback() # Rollback apenas desta operação
print("Preço inválido, operação revertida")
trans.commit()
except Exception as e:
trans.rollback()
print(f"Transação revertida: {e}")
# Batch insert otimizado
with engine.connect() as conn:
dados = [
{'nome': 'Monitor', 'preco': 1200.00, 'categoria': 'Informática'},
{'nome': 'Webcam', 'preco': 299.00, 'categoria': 'Periféricos'},
{'nome': 'Headset', 'preco': 199.00, 'categoria': 'Áudio'},
]
conn.execute(insert(produtos), dados)
conn.commit()
Boas práticas adicionais:
- Use parâmetros nomeados em vez de concatenação de strings para evitar SQL injection
- Prefira with engine.connect() para garantir fechamento automático
- Utilize executemany() para inserções em lote com melhor performance
- Configure pool_size e max_overflow no Engine para ambientes de produção
O SQLAlchemy Core oferece uma camada de abstração poderosa que combina a flexibilidade do SQL puro com a segurança e produtividade de uma API Pythonica. Para cenários que exigem performance máxima ou controle granular sobre as consultas, é frequentemente superior ao ORM.
Referências
- Documentação Oficial do SQLAlchemy Core — Guia completo sobre todas as funcionalidades do Core, incluindo tutorial e referência de API
- SQLAlchemy: Tutorial de Expressões SQL — Tutorial passo a passo para iniciar com expressões SQL usando o Core
- Real Python: SQLAlchemy Core vs ORM — Artigo comparativo detalhado entre as duas abordagens do SQLAlchemy
- SQLAlchemy Engine Configuration — Documentação sobre configuração de Engine, pooling e conexões
- SQLAlchemy: Trabalhando com Transações — Guia oficial sobre gerenciamento de transações e savepoints
- Python SQLAlchemy Tutorial (DataCamp) — Tutorial abrangente cobrindo desde conceitos básicos até operações avançadas
- SQLAlchemy: Funções e Agregações — Documentação das funções SQL disponíveis no Core, incluindo agregadas e window functions