📝 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 ... SET e DELETE FROM com filtros rigorosos.
  • 🔹 Compreender o perigo fatal de rodar UPDATE ou DELETE sem cláusula WHERE e como usar transações seguras de proteção.
  • 🔹 Utilizar recursos modernos de SQL: INSERT INTO ... SELECT, cláusula RETURNING e 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 UPDATE ou DELETE dentro de um START TRANSACTION é uma prática Sênior. Se você esquecer o WHERE e estragar a tabela, basta rodar ROLLBACK; em vez de COMMIT; 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.

  1. Insira o novo veículo (ID 30, Placa CCC5555, Status ATIVO).
  2. Atualize o status do veículo 10 para 'VIAGEM'.
  3. 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

Aviso

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

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 id autoincrementado gerado) sem precisar fazer um segundo SELECT em 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 um UPDATE automá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.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 06: SQL DML (MANIPULAÇÃO DE DADOS)