📝 CAPÍTULO 13: DML — INSERT, UPDATE, DELETE E RETURNING
🎯 Objetivos de Aprendizagem
Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:
- 🔹 Dominar os comandos de manipulação de dados:
INSERT INTO,UPDATE ... SETeDELETE FROMcom filtros rigorosos. - 🔹 Compreender o perigo fatal de rodar
UPDATEouDELETEsem cláusulaWHEREe como usar transações seguras de proteção. - 🔹 Utilizar recursos modernos de SQL:
INSERT INTO ... SELECT, cláusulaRETURNINGe operações de UPSERT (ON CONFLICT DO UPDATE). - 🔹 Implementar operações DML em lote (Bulk Operations) de alta performance no SQLAlchemy 2.0.
Se o DDL constrói a "casa", o DML (Data Manipulation Language) são os móveis e as pessoas dentro dela. O DML é a linguagem do dia a dia do desenvolvedor; é com ela que sua aplicação web cadastra usuários, altera senhas e exclui postagens. 🛡️🧩
🏢 O Cenário Prático (Seu Desafio)
Na TecProExpress, um desenvolvedor júnior precisava dar um aumento de 10% no salário do "João". Ele rodou o comando e, quando os holerites saíram, todos os 5.000 funcionários da empresa haviam recebido 10% de aumento. O prejuízo foi milionário.
"Seu desafio é treinar os estagiários para nunca mais cometerem esse erro fatal. Você deve demonstrar o uso seguro da DML, explicando como a cláusula
WHEREé o único escudo entre uma atualização pontual e a demissão sumária por erro em produção."
🧠 Fundamentos: Os Três Pilares da Manipulação
1. Inserindo (INSERT INTO)
O comando INSERT adiciona uma ou mais linhas (tuplas) à tabela.
-- DDL Rápido para contexto
-- CREATE TABLE cliente (id INT PRIMARY KEY, nome VARCHAR(50), saldo DECIMAL(10,2));
-- Inserção Explicita (A mais segura, você diz quais colunas)
INSERT INTO cliente (id, nome, saldo) VALUES (1, 'Maria', 500.00);
-- Inserção Implícita (Se você souber a ordem exata das colunas)
INSERT INTO cliente VALUES (2, 'José', 1000.00);
-- Múltipla (Várias linhas num único comando - Alta Performance)
INSERT INTO cliente (id, nome, saldo) VALUES
(3, 'Ana', 0),
(4, 'Carlos', 200);
2. Atualizando (UPDATE)
Altera dados que já existem. O maior perigo do SQL mora aqui.
-- O Jeito Certo (Uso obrigatório do WHERE)
UPDATE cliente
SET saldo = 800.00
WHERE id = 1; -- Apenas a Maria recebe o novo saldo
-- O Erro do Estagiário (NUNCA FAÇA ISSO)
-- UPDATE cliente SET saldo = 800.00;
-- Efeito: Maria, José, Ana e Carlos teriam seu saldo substituído para 800.
3. Excluindo (DELETE FROM)
Remove linhas inteiras da tabela.
-- Exclusão Segura
DELETE FROM cliente WHERE id = 4; -- O Carlos é removido do sistema
-- O Desastre (Evacuação Total)
-- DELETE FROM cliente;
-- Apaga absolutamente TODAS as linhas da tabela. Apenas a estrutura (DDL) sobrevive.
📊 Fluxo de Segurança Transacional em Operações DML
Para evitar acidentes com atualizações ou exclusões acidentais em produção, adota-se o fluxo de transação com confirmação explícita:
flowchart TD
A["Início: BEGIN TRANSACTION"] --> B["Executar DML com Filtro: UPDATE / DELETE WHERE id = :id"]
B --> C{"Verificação: Apenas 1 linha afetada?"}
C -- "Sim (Sucesso)" --> D["COMMIT (Grava permanentemente)"]
C -- "Não (Muitas linhas ou Erro)" --> E["ROLLBACK (Desfaz tudo sem danos)"]
style A fill:#e3f2fd,stroke:#1e88e5
style B fill:#fff8e1,stroke:#fbc02d
style D fill:#e8f5e9,stroke:#43a047
style E fill:#fdf2f2,stroke:#c0392b
📖 Exemplo Guiado: O Teste de Sobrevivência (DDL -> DML)
Sempre teste atualizações complexas antes de rodá-las em produção.
🛠️ Código do Exemplo (Com Segurança)
-- PASSO 1: DDL (Criando a Tabela)
CREATE TABLE frota_tecpro (
id_veiculo INT PRIMARY KEY,
placa VARCHAR(7),
status VARCHAR(20)
);
-- PASSO 2: DML (Carga Inicial)
INSERT INTO frota_tecpro VALUES (10, 'AAA1234', 'ATIVO');
INSERT INTO frota_tecpro VALUES (20, 'BBB9999', 'ATIVO');
-- PASSO 3: O Teste de Segurança (Usando transação)
START TRANSACTION;
UPDATE frota_tecpro SET status = 'MANUTENCAO' WHERE id_veiculo = 10;
COMMIT;
🔍 Detalhamento do Código:
- Envelopar um
UPDATEouDELETEdentro de umSTART TRANSACTIONé uma prática Sênior. Se você esquecer oWHEREe estragar a tabela, basta rodarROLLBACK;em vez deCOMMIT;para desfazer o erro antes que ele seja salvo no disco!
🛠️ Prática Obrigatória: A Manutenção da Frota
Cenário: A TecProExpress vendeu o veículo 20 e comprou um novo veículo 30.
- Insira o novo veículo (ID 30, Placa CCC5555, Status ATIVO).
- Atualize o status do veículo 10 para 'VIAGEM'.
- Exclua o veículo 20 do sistema.
🚀 Script de Seed (Gabarito DML)
-- 1. Insert
INSERT INTO frota_tecpro (id_veiculo, placa, status)
VALUES (30, 'CCC5555', 'ATIVO');
-- 2. Update seguro (Sempre use a Chave Primária no WHERE!)
UPDATE frota_tecpro
SET status = 'VIAGEM'
WHERE id_veiculo = 10;
-- 3. Delete seguro
DELETE FROM frota_tecpro
WHERE id_veiculo = 20;
💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM
Como o SQLAlchemy 2.0 transforma operações de DML (INSERT, UPDATE e DELETE) em um ciclo seguro de manipulação de objetos?
🔴 1. A Abordagem Manual (O Perigo de UPDATE e DELETE sem WHERE)
No SQL manual concatenado, esquecer um filtro WHERE causa desastres irreversíveis em produção:
# ❌ ABORDAGEM COM SQL MANUAL: Um descuido no WHERE apaga a frota inteira
placa_alvo = None
if placa_alvo:
cursor.execute(f"DELETE FROM frota WHERE placa = '{placa_alvo}';")
else:
# Se a variável estiver vazia, o comando executará: DELETE FROM frota; ⚠️
pass
🟢 2. A Abordagem com SQLAlchemy 2.0 (Padrão Repositório Orientado a Objetos)
Com o SQLAlchemy 2.0, manipulamos as instâncias em memória. O ORM rastreia as alterações (Unit of Work) e emite os comandos INSERT, UPDATE e DELETE usando sempre a Chave Primária no WHERE:
# ✅ ABORDAGEM MODERNA: Padrão Repositório CRUD Seguro
from typing import Any
from sqlalchemy import create_engine, String, Integer, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class FrotaTecProModel(Base):
__tablename__ = "frota_dml_segura"
id_veiculo: Mapped[int] = mapped_column(Integer, primary_key=True)
placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
status: Mapped[str] = mapped_column(String(20), default="ATIVO")
def __repr__(self) -> str:
return f"Veiculo(id={self.id_veiculo}, placa='{self.placa}', status='{self.status}')"
🛠️ Mini-Projeto 13 (BD): Repositório CRUD Seguro de Frota
Objetivo: Construir uma classe de serviço com operações seguras de inserção, consulta, atualização e deleção protegidas por PK.
📋 Pré-requisitos e Instalação
No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:
pip install sqlalchemy
💻 Código Completo e Autocontido (miniprojeto_13_crud.py)
Crie o arquivo miniprojeto_13_crud.py e insira o código abaixo integralmente:
"""
Mini-Projeto 13: Repositório CRUD Seguro de Frota
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Integer, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
# 1. Definição Declarativa do Schema
class Base(DeclarativeBase):
pass
class FrotaTecProModel(Base):
__tablename__ = "frota_dml_segura"
id_veiculo: Mapped[int] = mapped_column(Integer, primary_key=True)
placa: Mapped[str] = mapped_column(String(7), unique=True, nullable=False)
status: Mapped[str] = mapped_column(String(20), default="ATIVO")
def __repr__(self) -> str:
return f"Veiculo(id={self.id_veiculo}, placa='{self.placa}', status='{self.status}')"
# 2. Camada de Repositório Orientada a Objetos
class FrotaRepository:
def __init__(self, engine) -> None:
self.engine = engine
def criar_veiculo(self, id_veiculo: int, placa: str, status: str = "ATIVO") -> FrotaTecProModel:
with Session(self.engine) as session:
v = FrotaTecProModel(id_veiculo=id_veiculo, placa=placa, status=status)
session.merge(v)
session.commit()
return v
def atualizar_status(self, id_veiculo: int, novo_status: str) -> bool:
with Session(self.engine) as session:
veiculo = session.get(FrotaTecProModel, id_veiculo)
if veiculo:
# O SQLAlchemy emite UPDATE ... WHERE id_veiculo = :id automaticamente
veiculo.status = novo_status
session.commit()
return True
return False
def remover_veiculo(self, id_veiculo: int) -> bool:
with Session(self.engine) as session:
veiculo = session.get(FrotaTecProModel, id_veiculo)
if veiculo:
# O SQLAlchemy emite DELETE ... WHERE id_veiculo = :id automaticamente
session.delete(veiculo)
session.commit()
return True
return False
def listar_todos(self) -> list[FrotaTecProModel]:
with Session(self.engine) as session:
return list(session.scalars(select(FrotaTecProModel)).all())
# 3. Ponto de Entrada Executável
if __name__ == "__main__":
DB_FILE = "tecpro_crud.db"
# Reset preventivo para garantir idempotência em testes repetidos
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
Base.metadata.create_all(bind=engine)
repo = FrotaRepository(engine)
print("📝 REPOSITÓRIO CRUD SEGURO COM SQLALCHEMY 2.0")
print("=" * 60)
# 1. INSERT (Criando veículos)
repo.criar_veiculo(10, "AAA1234", "ATIVO")
repo.criar_veiculo(20, "BBB9999", "ATIVO")
repo.criar_veiculo(30, "CCC5555", "ATIVO")
print("1. [INSERT] 3 Veículos inseridos com sucesso.")
# 2. UPDATE Seguro (Atualizando veículo 10)
repo.atualizar_status(10, "VIAGEM")
print("2. [UPDATE] Veículo #10 atualizado para status 'VIAGEM'.")
# 3. DELETE Seguro (Removendo veículo 20)
repo.remover_veiculo(20)
print("3. [DELETE] Veículo #20 removido do sistema.")
# 4. SELECT (Listagem Final)
print("\n4. [SELECT] Estado Atual da Frota:")
for v in repo.listar_todos():
print(f" 🚛 {v}")
print("=" * 60)
🚀 Como Executar
Execute o script no terminal:
python miniprojeto_13_crud.py
🖥️ Saída Esperada no Console
📝 REPOSITÓRIO CRUD SEGURO COM SQLALCHEMY 2.0
============================================================
1. [INSERT] 3 Veículos inseridos com sucesso.
2. [UPDATE] Veículo #10 atualizado para status 'VIAGEM'.
3. [DELETE] Veículo #20 removido do sistema.
4. [SELECT] Estado Atual da Frota:
🚛 Veiculo(id=10, placa='AAA1234', status='VIAGEM')
🚛 Veiculo(id=30, placa='CCC5555', status='ATIVO')
============================================================
💡 Checkpoint de Lógica
Reflexão Profissional:
Diferente de editores de texto como o Word, comandos DML como UPDATE e DELETE sem controle transacional (como vimos no Cap 03) não possuem Ctrl+Z. Ao executar o comando, o disco rígido é reescrito instantaneamente. No MySQL Workbench, existe uma trava de segurança chamada Safe Updates que bloqueia UPDATEs que não usam a Chave Primária no WHERE, mas em servidores em nuvem, essa trava não existe! Respeite o WHERE! 🚀🛡️
🧪 Quiz de Fixação e Autoavaliação — Capítulo 13
🧪 Quiz de Fixação e Autoavaliação — Capítulo 13
1. O que acontece se um desenvolvedor executar o comando UPDATE funcionarios SET salario = 10000; sem colocar a cláusula WHERE?
- A) O banco dá erro de sintaxe por falta do WHERE.
- B) TODOS os funcionários da empresa terão seus salários alterados para R$ 10.000 instantaneamente, sobrescrevendo a base inteira!
- C) Apenas o primeiro funcionário cadastrado terá o salário alterado.
- D) O comando é ignorado.
💡 Ver Resposta e Justificativa
Resposta Correta: B
Justificativa: Sem WHERE, comandos UPDATE e DELETE afetam 100% das linhas da tabela. Em ambiente de produção, sempre execute dentro de uma transação com BEGIN; antes de comitar!
2. Qual é a utilidade da cláusula moderna RETURNING (nativa do PostgreSQL e SQLite 3.35+) em um comando INSERT?
- A) Desfazer o insert e voltar para o menu anterior.
-
B) Retornar imediatamente os valores das colunas geradas pelo banco (como o
idautoincrementado gerado) sem precisar fazer um segundoSELECTem seguida. - C) Enviar um e-mail com os dados inseridos.
- D) Calcular a média dos valores da tabela.
💡 Ver Resposta e Justificativa
Resposta Correta: B
Justificativa: INSERT INTO clientes (nome) VALUES ('Ana') RETURNING id; economiza round-trips de rede ao devolver o ID gerado na mesma operação.
3. O que é uma operação de 'UPSERT' (Merge / On Conflict) no SQL moderno?
- A) Uma operação que apaga a tabela se houver duplicidade.
-
B) Uma instrução que tenta fazer
INSERT, mas se encontrar conflito de chave única (UNIQUE/PK), executa umUPDATEautomático na linha existente. - C) Um comando que converte o banco para NoSQL.
- D) Uma atualização feita em letras maiúsculas.
💡 Ver Resposta e Justificativa
Resposta Correta: B
Justificativa: INSERT INTO estoque (sku, qtd) VALUES ('P01', 10) ON CONFLICT (sku) DO UPDATE SET qtd = estoque.qtd + 10; resolve concorrência de estoque perfeitamente.
Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 06: SQL DML (MANIPULAÇÃO DE DADOS)