Competições Senac RS · Seletiva 26

Módulo 03 · Apostila teórica

Módulo 03 — Banco de dados

BaseUC3 do plano de curso (Planejar e administrar banco de dados, 84h)Peso na prova10% direto (mas contamina os 20% de back e boa parte do front)NaturezaRevisão com aprofundamento
Ocupação: Desenvolvimento de Sistemas Treinador: Diogo Roehrs
💡Nota

Onde esse módulo entra. No Módulo B da prova (Banco + API, 2h30), a primeira coisa que se faz é modelar o banco. Ele é a fundação: se o modelo de dados está torto, a API vem torta, o front vem torto, e não tem acabamento que salve. O banco vale 10% direto, mas contamina os outros 60% de back e front. Aqui a natureza é 🔄 revisão, porque as duas já modelaram banco na Regional. Só que agora a revisão desce fundo: não é decorar comando, é entender por que cada decisão de modelagem existe, escolher certo em situações diferentes e conseguir defender a escolha na apresentação. Modelar certo, rápido e sabendo o porquê. Esse é o nível 3.


1. Tipos de banco de dados: relacional, não relacional e por que a prova é relacional

1.1 O que é um SGBD

Banco de dados é o dado organizado. SGBD (Sistema Gerenciador de Banco de Dados) é o software que administra esse dado: recebe comandos, garante as regras, controla acesso simultâneo, protege contra corrupção. MySQL, PostgreSQL, SQL Server e Oracle são SGBDs. Quando a gente fala "o banco barrou o insert", quem barrou foi o SGBD aplicando uma regra que você definiu.

O SQL (Structured Query Language) nasceu nos anos 70 na IBM (projeto System R, baseado no modelo relacional que o Edgar Codd propôs em 1970) e virou padrão ANSI/ISO nos anos 80. Por isso o grosso do SQL funciona igual em qualquer banco relacional: o SELECT que você escreve no MySQL é praticamente o mesmo do PostgreSQL. O que muda são detalhes de função e de tipo, e a gente vai trabalhar sempre no dialeto do MySQL 8, que é o da prova.

1.2 Relacional: tabelas, relações e garantias

No modelo relacional o dado mora em tabelas (linhas e colunas), as tabelas se ligam por chaves, e o SGBD garante um pacote de propriedades conhecido como ACID:

Propriedade O que garante Exemplo no Wedding Pass
Atomicidade Operação em grupo ou acontece inteira ou não acontece Criar convidado + convite juntos: se o convite falhar, o convidado não fica órfão
Consistência O banco nunca fica num estado que viola as regras Nenhum convite aponta pra convidado inexistente
Isolamento Duas operações simultâneas não se atropelam Dois check-ins do mesmo código ao mesmo tempo: só um passa
Durabilidade Confirmou, tá gravado, mesmo se faltar luz O check-in feito às 19h32 não some

Essas quatro letras são exatamente o que um sistema de gestão precisa. Guarda esse quadro: ele volta na seção de transações (17) e no check-in do módulo 07.

1.3 Não relacional (NoSQL): o que existe do outro lado

O plano de curso pede o comparativo, e o mercado usa os dois. NoSQL não é "melhor" nem "pior": é um conjunto de modelos diferentes pra problemas diferentes.

Família Como guarda Exemplo Brilha quando
Documento JSON aninhado, sem esquema fixo MongoDB Dados semiestruturados que mudam de forma (catálogo de produtos com atributos variados)
Chave-valor Um valor por chave, acesso direto Redis Cache, sessão, contador em tempo real
Colunar Colunas agrupadas, escrita massiva Cassandra Volume gigante de escrita (telemetria, logs)
Grafo Nós e arestas Neo4j Relacionamentos profundos (rede social, recomendação)

O trade-off central: NoSQL costuma abrir mão de parte das garantias ACID e dos JOINs pra ganhar flexibilidade e escala horizontal. Num sistema de gestão (convidados, convites, mesas, check-ins), essa troca é péssima: os dados são estruturados, se relacionam o tempo todo, e os relatórios (dashboard!) nascem de cruzamento de tabelas.

🎯Foco de prova

🎯 Por que a prova é relacional. Todo Projeto-Teste da nossa área é um sistema de gestão: entidades bem definidas, regras de integridade, relatórios agregados. Isso é o caso de uso clássico do relacional. Saber explicar isso em uma frase ("escolhi relacional porque os dados são estruturados e se relacionam, e eu preciso de integridade e de relatórios com JOIN") é repertório de apresentação que impressiona banca. NoSQL na prova é isso: repertório pra conversa, não ferramenta pra usar.


2. Os três níveis de modelagem: conceitual → lógico → físico

Modelar não é um passo só. O caminho profissional (e o que o plano de curso cobra) tem três níveis, cada um respondendo uma pergunta diferente:

Nível Pergunta Produto Se preocupa com
Conceitual O que o sistema precisa lembrar? MER (entidades, atributos, relacionamentos) O negócio. Zero tecnologia
Lógico Como isso vira tabelas? Tabelas, chaves, cardinalidades resolvidas Estrutura relacional. N:N vira tabela associativa aqui
Físico Como isso roda no MySQL? Script SQL com tipos exatos, constraints, índices O SGBD escolhido

2.1 O mesmo caminho numa situação diferente: sistema de escola

Pra provar que o método serve pra qualquer enunciado (e a Prova Surpresa vai trazer um enunciado que ninguém conhece), vamos modelar uma escola do zero:

Conceitual. Lendo o enunciado imaginário ("a escola tem turmas, alunos se matriculam em turmas, cada turma tem um professor responsável e as notas dos alunos ficam registradas"), sublinho os substantivos que o sistema precisa lembrar: aluno, turma, professor, matrícula, nota. Relações: professor 1:N turma (um professor responde por várias turmas), aluno N:N turma (um aluno em várias turmas, uma turma com vários alunos), e a nota pertence... a quê? Não ao aluno sozinho nem à turma sozinha: à matrícula (aquele aluno naquela turma). Isso é achado de modelagem: a nota é atributo do relacionamento.

Lógico. O N:N aluno-turma vira a tabela matricula (aluno_id, turma_id), e a nota vira coluna dela (ou tabela avaliacao ligada à matrícula, se forem várias notas). turma ganha professor_id. Todas as tabelas ganham chave primária.

Físico. Agora sim: matricula.nota vira DECIMAL(4,1), aluno.nome vira VARCHAR(100) NOT NULL, matricula ganha UNIQUE(aluno_id, turma_id) pra ninguém se matricular duas vezes na mesma turma, e as FKs ganham regra de deleção.

2.2 O mesmo padrão em pedidos de e-commerce

Outro cenário clássico, porque ele ensina uma decisão que cai em prova: pedido e produto são N:N (um pedido tem vários produtos, um produto aparece em vários pedidos), então nasce item_pedido(pedido_id, produto_id, quantidade, preco_unitario). Repara no preco_unitario dentro do item: o preço do produto muda com o tempo, mas o pedido precisa lembrar quanto custava na hora da compra. Copiar o preço ali não é erro de normalização, é snapshot histórico de propósito (a gente volta nisso na seção 5.5).

🛠️Gambiarra boa

🛠️ Gambiarra boa: os primeiros 10 minutos do Módulo B são de papel. Conceitual no rascunho: caixas, linhas, cardinalidades. Só depois abre o MySQL. Quem modela direto no SQL descobre a tabela faltando no meio do CREATE e refaz tudo. Dez minutos de papel economizam trinta de retrabalho, e o rascunho ainda vira roteiro da apresentação ("essa foi minha modelagem inicial").

⚠️ Pega-ratão: pular o nível conceitual "porque o sistema é pequeno". O sistema da prova nunca é tão pequeno quanto parece: o enunciado esconde entidades (o RSVP esconde um status com data, o check-in esconde um registro com hora). O conceitual é onde essas coisas aparecem antes de custar caro.


3. Entidade, atributo, relacionamento e cardinalidade: a leitura fina

3.1 Achar entidades num enunciado

Entidade é uma coisa sobre a qual o sistema guarda informação com identidade própria. O truque de leitura: substantivos que o sistema precisa lembrar depois. No Wedding Pass: usuário, casamento, convidado, convite, mesa, check-in. "Relatório" não é entidade (é uma consulta). "Status" sozinho não é entidade (é atributo de alguém).

3.2 Tipos de atributo (e uma regra de ouro)

3.3 Cardinalidade com mínimo e máximo

Além do 1:1, 1:N e N:N, o modelo fino marca o mínimo: a relação é obrigatória ou opcional?

Essa leitura de mínimo é o que decide, lá no físico, se a FK é NOT NULL ou não. E é ela que evita a armadilha da obrigatoriedade dos dois lados: se "todo departamento tem ao menos um funcionário" e "todo funcionário tem departamento", como é que se insere o primeiro registro num banco vazio? (Segura essa pergunta: ela é a porta de entrada da ligação circular, seção 6.)

3.4 Resolvendo cada cardinalidade

⚠️Pega-ratão

⚠️ Pega-ratão: resolver N:N com lista dentro de campo ("João, Maria, Ana" numa coluna). Quebra busca, quebra contagem, quebra integridade, quebra 1FN. N:N pede tabela associativa, sempre. Se você se pegar querendo guardar uma lista numa coluna, tá faltando uma tabela.

🎯 Isso é a habilidade da Prova Surpresa. O Módulo A dá um enunciado desconhecido e 2h. Quem tem o método (substantivos → entidades → cardinalidades → FK no lado certo) modela qualquer domínio em minutos. O módulo 07 transforma isso num passo a passo completo (enunciado → entidades → endpoints → telas).


4. Chaves: o sistema de identidade do banco

🎯Foco de prova

🎯 Foco de prova: a banca verifica objetivamente se as tabelas estão de fato relacionadas por FK no banco, ou só "de mentira" (colunas soltas com nome parecido). FOREIGN KEY declarado é sim/não no CIS. E a escolha surrogate + natural como UNIQUE é um "porquê" pronto pra apresentação.


5. Normalização: 1FN, 2FN e 3FN com a tabela errada na mesa

Normalizar é eliminar redundância e dependência mal colocada. A teoria fica clara quando se parte do erro. Eis a "tabelona" que todo mundo já fez na vida (uma planilha virada tabela):

convidados_geral
| id | convidado      | telefones           | casamento     | data_casamento | mesa | capacidade_mesa |
| 1  | Ana Souza      | 5199..., 5198...    | Júlia & Pedro | 2026-11-21     | 5    | 8               |
| 2  | Bruno Lima     | 5197...             | Júlia & Pedro | 2026-11-21     | 5    | 8               |
| 3  | Carla Dias     | 5196..., 5195...    | Júlia & Pedro | 2026-11-21     | 2    | 10              |

5.1 As três anomalias que essa tabela cria

Normalização é o antídoto dessas três.

5.2 1FN: valores atômicos, sem grupos repetidos

Cada célula guarda um valor. telefones com dois números viola. E a variação "resolvida" com telefone1, telefone2, telefone3 também viola (grupo repetido: e o quarto telefone?).

Correção: telefone vira tabela filha.

CREATE TABLE telefone (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  convidado_id INT UNSIGNED NOT NULL,
  numero VARCHAR(15) NOT NULL,
  FOREIGN KEY (convidado_id) REFERENCES convidado(id)
);

(Na prova, se o enunciado pede um telefone por convidado, uma coluna resolve. 1FN pega quando são vários.)

5.3 2FN: todo campo depende da chave inteira

Só faz sentido com chave composta. Exemplo: tabela de presença com PK (convidado_id, evento_id):

presenca
| convidado_id | evento_id | nome_convidado | status     |

status depende do par completo (aquele convidado naquele evento): ok. nome_convidado depende só de convidado_id: violação de 2FN. O nome já mora na tabela convidado; aqui é cópia que vai dessincronizar.

Correção: tirar nome_convidado e buscar por JOIN quando precisar.

5.4 3FN: nenhum campo depende de outro campo não chave

Na tabelona: capacidade_mesa depende de mesa, e mesa não é chave. Isso é dependência transitiva (id → mesa → capacidade). Resultado: a capacidade da mesa 5 repetida em toda linha de convidado da mesa 5, e a anomalia de exclusão da Carla.

Correção: mesa vira tabela própria (id, numero, capacidade), convidado ganha mesa_id.

5.5 O resultado normalizado (e quando parar)

casamento(id, nome, data_evento, ...)
mesa(id, casamento_id, numero, capacidade)
convidado(id, casamento_id, mesa_id NULL, nome, ...)
telefone(id, convidado_id, numero)

Cada informação mora num lugar só. As três anomalias morreram.

Desnormalizar de propósito é ferramenta de gente grande, não desculpa de preguiça: o preco_unitario no item do pedido (snapshot histórico) e contadores cacheados em sistemas de altíssimo volume são casos legítimos. A diferença entre desnormalização e erro é uma só: você sabe dizer por quê.

🛠️Gambiarra boa

🛠️ Gambiarra boa (a regra de bolso). Na hora da prova ninguém recita definição de forma normal. O instinto que elas geram é o que se aplica: cada informação mora num lugar só. Se você está copiando o mesmo dado em várias linhas, falta uma tabela. Se um campo não fala sobre a "coisa" daquela tabela, ele está no lugar errado. Mas a definição precisa estar na cabeça também, porque banca pergunta ("por que você separou a mesa?" → "dependência transitiva, terceira forma normal" é resposta de nível 3).

⚠️ Pega-ratão do outro extremo: normalizar demais. Vinte tabelas microscópicas numa prova de 2h30 atrasam o CREATE, complicam cada JOIN e não pontuam mais por isso. 3FN com bom senso é o equilíbrio: organizado e prático.


6. Ligação circular: o que é, quando é problema e como resolver

Ligação circular (referência circular) é quando as FKs formam um ciclo: seguindo as setas de dependência, você volta pro ponto de partida. Existem três formas, e elas têm veredictos diferentes. Isso importa porque a pergunta "isso é errado?" não tem uma resposta só: tem três.

6.1 Auto-relacionamento (a tabela aponta pra ela mesma): legítimo e elegante

CREATE TABLE funcionario (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(100) NOT NULL,
  gerente_id INT UNSIGNED NULL,
  FOREIGN KEY (gerente_id) REFERENCES funcionario(id)
);

Todo funcionário pode ter um gerente, que também é funcionário. Isso modela hierarquia com uma tabela só: organograma, categoria com subcategoria, comentário com resposta, menu com submenu. Não é erro nenhum; é padrão clássico.

Os dois cuidados que fazem ele funcionar:

  1. A FK aceita NULL. Alguém está no topo (a diretora não tem gerente). Sem NULL permitido, ninguém consegue ser o primeiro inserido.
  2. Consultar hierarquia profunda exige recursão. "Quem responde pra Ana, direta e indiretamente?" não sai com um JOIN simples. Um nível sai com self-join (seção 14); a árvore inteira pede CTE recursiva (WITH RECURSIVE, que o MySQL 8 tem). Na prova, quase sempre um nível basta.

6.2 Ciclo entre duas tabelas (A → B e B → A): sinal amarelo, resolvível

O caso clássico:

-- departamento tem um chefe (que é funcionário)
-- funcionário pertence a um departamento
departamento(id, nome, chefe_id → funcionario.id)
funcionario(id, nome, departamento_id → departamento.id)

Aqui nasce o problema do ovo e da galinha: pra inserir o departamento preciso do chefe, pra inserir o chefe preciso do departamento. Com as duas FKs NOT NULL, o banco vazio trava: nenhum insert é possível. E o espelho disso acontece no DELETE: com RESTRICT dos dois lados, nenhuma das duas linhas consegue ser apagada primeiro.

Como resolver (em ordem de preferência):

  1. Quebrar o ciclo deixando uma FK aceitar NULL. departamento.chefe_id NULL: cria o departamento sem chefe, insere o funcionário, faz o UPDATE apontando o chefe. Duas etapas, problema resolvido. Escolhe pra ser anulável o lado cuja ausência temporária faz sentido no negócio (departamento sem chefe por uns dias é normal; funcionário sem departamento talvez não).
  2. Repensar se as duas FKs precisam existir. Muitas vezes uma delas é derivável ou modelável de outro jeito: um cargo na tabela de funcionário ("chefe") + a FK de departamento resolve sem ciclo nenhum. Ciclo que dá pra dissolver é ciclo que deveria ser dissolvido.
  3. Última instância (fora da prova): inserir os dois dentro de uma transação com verificação adiada. O MySQL/InnoDB não suporta constraint adiada (DEFERRABLE, coisa do PostgreSQL), então no MySQL essa porta nem existe. Mais um motivo pra resolver no modelo.

6.3 Ciclo longo (A → B → C → A): cheiro forte de modelagem errada

Quando o ciclo atravessa três ou mais tabelas, quase sempre alguma daquelas FKs não representa um relacionamento real, e sim uma tentativa de atalho ("vou guardar o casamento_id no check-in pra não fazer JOIN"). O check-in já chega no casamento via convidado; a FK extra é redundante, pode dessincronizar e ainda fecha o ciclo. Regra prática: cada fato do negócio entra no modelo uma vez. Se uma FK é derivável seguindo outras FKs, ela provavelmente não deveria existir.

6.4 No Wedding Pass

O modelo natural é acíclico: casamento ← mesa ← convidado ← convite / checkin, tudo apontando "pra cima" sem voltar. Um ciclo apareceria se, por exemplo, a mesa ganhasse um convidado_responsavel_id (mesa → convidado → mesa). Se o enunciado pedir "responsável pela mesa", a solução da seção 6.2 se aplica direto: convidado_responsavel_id NULL, preenchido depois que os convidados existem.

🎯Foco de prova

🎯 Resposta pronta pra banca (e pro Diogo 🙂): "ligação circular é errado?" Auto-relacionamento não é errado, é o padrão certo pra hierarquia. Ciclo entre tabelas é sinal amarelo: trava inserts e deletes, e se resolve quebrando o ciclo com uma FK anulável ou repensando o modelo. Ciclo longo quase sempre denuncia FK redundante. Saber essa resposta em três frases é diferencial de apresentação, porque a maioria só sabe dizer "acho que é ruim".

⚠️ Pega-ratão: obrigatoriedade nos dois lados do relacionamento (todo X tem Y e todo Y tem X, ambos NOT NULL). Mesmo sem FK circular declarada, isso cria o ovo-e-galinha na prática. Sempre pergunte: "com o banco vazio, qual linha entra primeiro?" Se a resposta é "nenhuma", o modelo tem um nó.


7. Integridade referencial: CASCADE, SET NULL e RESTRICT com casos reais

Integridade referencial é a garantia de que nenhuma FK aponta pro vazio. O banco cuida disso reagindo quando alguém apaga ou altera a linha "pai". A reação é você quem escolhe, na declaração da FK:

FOREIGN KEY (convidado_id) REFERENCES convidado(id)
  ON DELETE CASCADE
  ON UPDATE CASCADE
Regra Ao apagar o pai... Use quando o filho...
RESTRICT / NO ACTION O banco barra a exclusão Não pode perder o pai (registro histórico, financeiro)
CASCADE Os filhos somem junto Só existe em função do pai (convite sem convidado não significa nada)
SET NULL A FK do filho vira NULL Sobrevive sem o pai (a FK precisa aceitar NULL)

(No MySQL/InnoDB, RESTRICT e NO ACTION se comportam igual, e o padrão quando você não escreve nada é esse. ON UPDATE CASCADE cobre o caso raro de a PK do pai mudar; com AUTO_INCREMENT isso quase não acontece, mas declarar não custa.)

7.1 As decisões do Wedding Pass, uma a uma

FK Regra Porquê (esse é o texto da tua apresentação)
convite.convidado_id CASCADE Convite é papel do convidado. Apagou o convidado, o convite perde o sentido
convidado.mesa_id SET NULL Apagou a mesa, o convidado continua existindo, só fica sem lugar marcado
convidado.casamento_id CASCADE (ou RESTRICT) Apagar o casamento = limpar o evento inteiro (CASCADE); ou proibir apagar casamento com convidados (RESTRICT). As duas defendem; escolhe e justifica
checkin.convidado_id RESTRICT Check-in é registro de fato: aquela pessoa entrou. Apagar convidado que já entrou silenciosamente some com histórico. Barrar e exigir decisão explícita é o comportamento seguro
mesa.casamento_id CASCADE Mesa só existe dentro de um casamento

Repara que não existe UMA resposta certa pra tudo: existe resposta defendida. "Escolhi RESTRICT no check-in porque é registro histórico" é frase de nível 3.

⚠️Pega-ratão

⚠️ Pega-ratão: o CASCADE em cadeia. casamento CASCADE→ convidado CASCADE→ convite/checkin: um DELETE FROM casamento WHERE id = 1 varre o evento inteiro em silêncio. Às vezes é exatamente o que se quer; às vezes é meio banco sumindo por um clique. Antes de pôr CASCADE, siga a cadeia até o fim e pergunte "tudo isso deve sumir junto?". E no front, ação com CASCADE embaixo SEMPRE pede confirmação destrutiva (módulo 08).

🎯 Foco de prova: o descritivo cita integridade referencial explicitamente, e ela é verificável de forma objetiva (a banca tenta apagar um pai com filhos e observa). Fazer o banco impedir estado inválido é mais forte que confiar que a aplicação nunca erra. Essa frase também é da apresentação.


8. Constraints: o banco dizendo "não" (defesa em profundidade)

Além das chaves, o banco impõe regras nos campos. São a última linha de defesa: mesmo que a API tenha bug, mesmo que alguém insira na mão, a regra segura.

CREATE TABLE mesa (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  casamento_id INT UNSIGNED NOT NULL,          -- NOT NULL: obrigatório
  numero INT UNSIGNED NOT NULL,
  capacidade INT UNSIGNED NOT NULL,
  CONSTRAINT chk_capacidade CHECK (capacidade BETWEEN 1 AND 50),  -- CHECK: regra de valor
  CONSTRAINT uq_mesa_numero UNIQUE (casamento_id, numero),        -- UNIQUE composto
  FOREIGN KEY (casamento_id) REFERENCES casamento(id) ON DELETE CASCADE
);

O UNIQUE que ganha jogo: UNIQUE (convidado_id) na tabela checkin é o bloqueio de dupla entrada garantido no nível mais baixo. Nem bug de API, nem dois cliques rápidos, nem duas abas abertas furam. O módulo 07 constrói a feature completa em cima dessa constraint.

🛠️Gambiarra boa

🛠️ Gambiarra boa: deixa o banco trabalhar por você. Toda regra que vira constraint é regra que nunca será furada e que a aplicação valida "de graça" (a API só precisa traduzir o erro do banco pra mensagem bonita). Constraint é a defesa mais barata e mais confiável do sistema inteiro. A validação da API (módulo 04) existe pra dar mensagem boa; a constraint existe pra garantir a verdade.


9. Tipos de dados MySQL: escolher certo é pontuar de graça

Tipo errado funciona no dia 1 e explode no dia 30 (ou na frente da banca). A tabela de decisão:

Dado Tipo certo Porquê Erro comum
id INT UNSIGNED AUTO_INCREMENT 4 bytes, até ~4,29 bi sem sinal; sobra pra prova BIGINT à toa (dobra o espaço de todo índice)
Nome, e-mail VARCHAR(100) / VARCHAR(150) Tamanho variável, só ocupa o que usa TEXT pra campo curto (não pode ter DEFAULT, índice pior)
CPF, telefone, CEP VARCHAR Tem zero à esquerda e ninguém soma CPF INT (come o zero à esquerda: CPF 012... vira 12...)
Dinheiro DECIMAL(10,2) Exato. 0.10 é 0.10 FLOAT/DOUBLE: binário não representa 0.1 direito, e centavos somem em soma grande
Data do evento DATE Só a data, compara e calcula VARCHAR "21/11/2026" (não ordena, não entra em DATEDIFF)
Momento do check-in DATETIME Data + hora, range gigante —
criado_em / atualizado_em DATETIME DEFAULT CURRENT_TIMESTAMP (e ON UPDATE CURRENT_TIMESTAMP no segundo) Auditoria de graça Preencher na aplicação e esquecer em algum INSERT
Status ENUM('pendente','confirmado','recusado') Legível, compacto, valida sozinho VARCHAR livre (aceita "confirmadu")
Sim/não TINYINT(1) (o BOOLEAN do MySQL) Padrão da casa VARCHAR "sim"/"não"
Texto longo (observações) TEXT Até 64KB —

DATETIME vs TIMESTAMP, porque banca gosta: os dois guardam data+hora. TIMESTAMP converte pro fuso do servidor e só vai até janeiro de 2038 (4 bytes com época Unix); DATETIME guarda literal o que você mandou e vai do ano 1000 ao 9999. Regra prática do nosso contexto: DATETIME em tudo, com DEFAULT CURRENT_TIMESTAMP onde precisar de carimbo automático. Simples e sem surpresa de fuso.

ENUM vs tabela de domínio, a decisão honesta: ENUM é perfeito quando a lista é pequena e estável (status de convite não ganha valores novos toda semana). Se a lista cresce ou tem dados próprios (categorias com descrição e cor), aí é tabela + FK. Na prova, status é ENUM e segue o jogo; saber verbalizar o trade-off é o bônus.

⚠️Pega-ratão

⚠️ Pega-ratão: FLOAT pra dinheiro. É o erro de tipo mais clássico que existe, e é dos que a banca conhece de cor. DECIMAL(10,2) custa o mesmo pra escrever e é a resposta certa em qualquer sistema com valor monetário. Grava como reflexo.


10. O script do banco: DDL completo do Wedding Pass

Tudo que veio até aqui, materializado. Esse script é referência de estudo e esqueleto pra treinar (a prova terá outro domínio, mas o formato é esse):

DROP DATABASE IF EXISTS wedding_pass;
CREATE DATABASE wedding_pass CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE wedding_pass;

-- Ordem de criação: pais antes de filhos (a FK exige que o alvo exista)

CREATE TABLE usuario (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(100) NOT NULL,
  email VARCHAR(150) NOT NULL,
  senha_hash VARCHAR(255) NOT NULL,            -- hash bcrypt, NUNCA a senha (seção 18.3)
  perfil ENUM('admin','organizador','recepcao') NOT NULL DEFAULT 'recepcao',
  criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT uq_usuario_email UNIQUE (email)
) ENGINE=InnoDB;

CREATE TABLE casamento (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(150) NOT NULL,                  -- "Júlia & Pedro"
  data_evento DATETIME NOT NULL,
  local VARCHAR(200) NOT NULL,
  capacidade_total INT UNSIGNED NOT NULL,
  criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT chk_capacidade_total CHECK (capacidade_total > 0)
) ENGINE=InnoDB;

CREATE TABLE mesa (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  casamento_id INT UNSIGNED NOT NULL,
  numero INT UNSIGNED NOT NULL,
  capacidade INT UNSIGNED NOT NULL,
  CONSTRAINT chk_mesa_capacidade CHECK (capacidade BETWEEN 1 AND 50),
  CONSTRAINT uq_mesa_numero UNIQUE (casamento_id, numero),
  FOREIGN KEY (casamento_id) REFERENCES casamento(id) ON DELETE CASCADE
) ENGINE=InnoDB;

CREATE TABLE convidado (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  casamento_id INT UNSIGNED NOT NULL,
  mesa_id INT UNSIGNED NULL,                   -- (0,1): pode não ter mesa ainda
  nome VARCHAR(100) NOT NULL,
  email VARCHAR(150) NULL,
  telefone VARCHAR(15) NULL,
  acompanhantes TINYINT UNSIGNED NOT NULL DEFAULT 0,
  criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT chk_acompanhantes CHECK (acompanhantes <= 5),
  FOREIGN KEY (casamento_id) REFERENCES casamento(id) ON DELETE CASCADE,
  FOREIGN KEY (mesa_id) REFERENCES mesa(id) ON DELETE SET NULL
) ENGINE=InnoDB;

CREATE TABLE convite (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  convidado_id INT UNSIGNED NOT NULL,
  codigo CHAR(8) NOT NULL,                     -- código de negócio, aleatório, não sequencial
  status ENUM('pendente','confirmado','recusado') NOT NULL DEFAULT 'pendente',
  enviado_em DATETIME NULL,
  respondido_em DATETIME NULL,
  CONSTRAINT uq_convite_codigo UNIQUE (codigo),
  CONSTRAINT uq_convite_convidado UNIQUE (convidado_id),  -- um convite por convidado
  FOREIGN KEY (convidado_id) REFERENCES convidado(id) ON DELETE CASCADE
) ENGINE=InnoDB;

CREATE TABLE checkin (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  convidado_id INT UNSIGNED NOT NULL,
  registrado_por INT UNSIGNED NOT NULL,        -- qual usuário da recepção registrou
  registrado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT uq_checkin_convidado UNIQUE (convidado_id),  -- 🔒 bloqueio de dupla entrada
  FOREIGN KEY (convidado_id) REFERENCES convidado(id) ON DELETE RESTRICT,
  FOREIGN KEY (registrado_por) REFERENCES usuario(id) ON DELETE RESTRICT
) ENGINE=InnoDB;

Anatomia das decisões (cada uma é um "porquê" de apresentação):

🛠️Gambiarra boa

🛠️ Gambiarra boa: um arquivo schema.sql versionado no Git desde o dia 1. Banco não se cria clicando no Workbench sem registro: se cria por script, que roda inteiro, do zero, quantas vezes precisar. O script é a memória, a evidência pra banca e o botão de pânico (deu ruim? dropa e roda de novo, 10 segundos).


11. Dicionário de dados: o mapa que acompanha o modelo

Dicionário de dados é a documentação tabular do banco: cada tabela, cada coluna, tipo, regra e significado. O plano de curso pede, e a banca reconhece na hora. Formato enxuto, exemplo pra convite:

Coluna Tipo Nulo? Regra Descrição
id INT UNSIGNED não PK, auto Identificador interno
convidado_id INT UNSIGNED não FK → convidado, UNIQUE, CASCADE Dono do convite (um por convidado)
codigo CHAR(8) não UNIQUE, gerado aleatório Código que o convidado usa no RSVP e check-in
status ENUM não pendente/confirmado/recusado, DEFAULT pendente Situação da resposta
enviado_em DATETIME sim — Quando o convite foi disparado
respondido_em DATETIME sim — Quando o convidado respondeu
🛠️Gambiarra boa

🛠️ Gambiarra boa: o dicionário de prova se escreve em 5 minutos DEPOIS que o schema.sql está pronto, praticamente transcrevendo o script pra tabela. Custo mínimo, cara de documentação profissional, e ainda organiza a tua própria cabeça pra apresentação. Se o tempo apertar demais, o script comentado (como o da seção 10) já cumpre meio papel.


12. Manipulação: INSERT, UPDATE e DELETE sem sustos

-- INSERT simples
INSERT INTO convidado (casamento_id, nome, email, telefone)
VALUES (1, 'Ana Souza', 'ana@email.com', '51999990000');

-- INSERT múltiplo (a base do seed): uma instrução, várias linhas
INSERT INTO mesa (casamento_id, numero, capacidade) VALUES
  (1, 1, 8), (1, 2, 8), (1, 3, 10), (1, 4, 6);

-- INSERT ... SELECT: inserir a partir de consulta (gerar convites pra quem não tem)
INSERT INTO convite (convidado_id, codigo)
SELECT c.id, UPPER(SUBSTRING(MD5(RAND()), 1, 8))
FROM convidado c
LEFT JOIN convite cv ON cv.convidado_id = c.id
WHERE cv.id IS NULL;

-- UPDATE: sempre com WHERE
UPDATE convite SET status = 'confirmado', respondido_em = NOW()
WHERE codigo = 'A3F9K2LM';

-- DELETE: sempre com WHERE
DELETE FROM convidado WHERE id = 42;
⚠️Pega-ratão

⚠️ Pega-ratão (o clássico dos clássicos): UPDATE/DELETE sem WHERE. Atualiza ou apaga a tabela INTEIRA, sem confirmação, sem volta. Na pressa da prova é um desastre real.

🛠️ Gambiarra boa: o ritual do SELECT antes. Antes de qualquer UPDATE/DELETE delicado, roda um SELECT * FROM tabela WHERE <mesma condição> e olha o que volta. É exatamente o conjunto que vai ser afetado. Conferiu, troca o SELECT * por UPDATE ... SET ou DELETE. Dez segundos que blindam contra o pior erro possível.


13. Consultas: SELECT do básico ao afiado

13.1 Filtros

SELECT nome, email FROM convidado
WHERE casamento_id = 1
  AND mesa_id IS NULL                         -- NULL se testa com IS, nunca com =
  AND nome LIKE 'A%'                          -- começa com A ('%A%' = contém)
  AND acompanhantes BETWEEN 1 AND 3
  AND id IN (SELECT convidado_id FROM convite WHERE status = 'pendente')
ORDER BY nome ASC
LIMIT 10 OFFSET 0;                            -- página 1 com 10 itens (paginação, módulo 04)

Pontos finos: NULL não é igual a nada, nem a outro NULL (WHERE mesa_id = NULL não acha NADA; é IS NULL). LIKE '%texto%' com curinga no começo não usa índice (seção 16), mas na escala da prova não dói. AND tem precedência sobre OR: misturou os dois, usa parênteses.

13.2 Funções que resolvem prova

SELECT
  CONCAT(nome, ' (mesa ', COALESCE(mesa_id, 'sem mesa'), ')') AS etiqueta,
  UPPER(nome) AS nome_maiusculo,
  COALESCE(email, 'sem e-mail') AS contato,       -- primeiro valor não nulo
  IF(acompanhantes > 0, 'com acompanhante', 'sozinho') AS tipo,
  CASE
    WHEN acompanhantes = 0 THEN 'individual'
    WHEN acompanhantes <= 2 THEN 'família pequena'
    ELSE 'família grande'
  END AS faixa
FROM convidado;

13.3 Datas: o grupo de função que mais cai

SELECT
  NOW(),                                         -- data e hora agora
  CURDATE(),                                     -- só a data de hoje
  DATEDIFF(ca.data_evento, CURDATE()) AS dias_faltando,
  DATE_FORMAT(ca.data_evento, '%d/%m/%Y %H:%i') AS data_br,
  DATE_ADD(ca.data_evento, INTERVAL -7 DAY) AS prazo_rsvp
FROM casamento ca;

-- Filtrar por período (check-ins da última hora)
SELECT * FROM checkin
WHERE registrado_em >= NOW() - INTERVAL 1 HOUR;

DATEDIFF devolve dias; pra outras unidades, TIMESTAMPDIFF(MINUTE, inicio, fim). DATE_FORMAT formata pra exibir (%d/%m/%Y é o formato BR), mas guarda-se SEMPRE no tipo de data nativo: formatação é saída, não armazenamento.


14. JOINs: onde o relacional mostra a que veio

JOIN junta linhas de tabelas relacionadas, casando pela condição do ON (quase sempre FK = PK).

-- INNER JOIN: só quem tem correspondência dos dois lados
-- "convidados COM convite, mostrando o status"
SELECT c.nome, cv.codigo, cv.status
FROM convidado c
INNER JOIN convite cv ON cv.convidado_id = c.id;

-- LEFT JOIN: todos da esquerda, com ou sem par na direita
-- "TODOS os convidados, e o convite de quem tiver"
SELECT c.nome, cv.status              -- cv.status vem NULL pra quem não tem convite
FROM convidado c
LEFT JOIN convite cv ON cv.convidado_id = c.id;

-- O padrão-ouro "o que está FALTANDO": LEFT JOIN + IS NULL
-- "convidados SEM convite" (é a query que alimentou o INSERT...SELECT da seção 12)
SELECT c.nome
FROM convidado c
LEFT JOIN convite cv ON cv.convidado_id = c.id
WHERE cv.id IS NULL;

-- Três tabelas (vai encadeando): convidado + mesa + status do convite
SELECT c.nome, m.numero AS mesa, cv.status
FROM convidado c
LEFT JOIN mesa m ON m.id = c.mesa_id
LEFT JOIN convite cv ON cv.convidado_id = c.id
ORDER BY m.numero, c.nome;

-- Self-join (o auto-relacionamento da seção 6.1 em ação)
SELECT f.nome AS funcionario, g.nome AS gerente
FROM funcionario f
LEFT JOIN funcionario g ON g.id = f.gerente_id;

Guia rápido de escolha: INNER quando só interessa quem tem par. LEFT quando a lista da esquerda precisa vir completa (lista de convidados com dados opcionais ao lado: quase sempre é LEFT). RIGHT existe, mas todo RIGHT vira um LEFT invertendo a ordem das tabelas; o mercado escreve LEFT e pronto. UNION empilha resultados de dois SELECTs com as mesmas colunas (UNION ALL mantém duplicados e é mais rápido; UNION deduplica).

⚠️Pega-ratão

⚠️ Pega-ratão: esquecer a condição do JOIN (ou errar ela). Sem ON correto o banco cruza todo mundo com todo mundo (produto cartesiano): 50 convidados × 50 convites = 2500 linhas de lixo. Se uma consulta devolver muito mais linha que o esperado, o primeiro suspeito é o ON.

🛠️ Gambiarra boa: apelido curto pra toda tabela (convidado c, convite cv) e coluna sempre prefixada (c.nome). Em JOIN de 3+ tabelas com colunas de nome igual (id, nome), isso evita o erro "ambiguous column" e deixa a query legível pra banca.


15. Agrupamento e agregação: o SQL que vira dashboard

Funções de agregação resumem linhas: COUNT, SUM, AVG, MIN, MAX. Com GROUP BY, o resumo sai por grupo. E aqui mora o dashboard inteiro do Wedding Pass:

-- Os números do topo do dashboard
SELECT
  COUNT(*) AS total_convidados,
  SUM(acompanhantes) AS total_acompanhantes,
  COUNT(*) + SUM(acompanhantes) AS pessoas_esperadas
FROM convidado WHERE casamento_id = 1;

-- Convites por status (o gráfico de pizza)
SELECT cv.status, COUNT(*) AS quantidade
FROM convite cv
JOIN convidado c ON c.id = cv.convidado_id
WHERE c.casamento_id = 1
GROUP BY cv.status;

-- Ocupação por mesa (COUNT(c.id), não COUNT(*): mesa vazia conta 0)
SELECT m.numero, m.capacidade,
       COUNT(c.id) AS ocupacao,
       ROUND(COUNT(c.id) / m.capacidade * 100) AS pct
FROM mesa m
LEFT JOIN convidado c ON c.mesa_id = m.id
WHERE m.casamento_id = 1
GROUP BY m.id, m.numero, m.capacidade;

-- Só as mesas acima de 90% (o ALERTA do enunciado): HAVING filtra o grupo
SELECT m.numero, COUNT(c.id) / m.capacidade * 100 AS pct
FROM mesa m
LEFT JOIN convidado c ON c.mesa_id = m.id
GROUP BY m.id, m.numero, m.capacidade
HAVING pct >= 90;

-- Lotação em tempo real: quem já entrou (check-ins) sobre a capacidade
SELECT COUNT(ck.id) AS presentes, ca.capacidade_total,
       ROUND(COUNT(ck.id) / ca.capacidade_total * 100, 1) AS lotacao_pct
FROM casamento ca
LEFT JOIN convidado c ON c.casamento_id = ca.id
LEFT JOIN checkin ck ON ck.convidado_id = c.id
WHERE ca.id = 1
GROUP BY ca.id, ca.capacidade_total;

-- Check-ins por hora (o gráfico de linha da festa)
SELECT DATE_FORMAT(registrado_em, '%H:00') AS hora, COUNT(*) AS entradas
FROM checkin
GROUP BY hora
ORDER BY hora;

WHERE vs HAVING, de uma vez por todas: WHERE filtra linhas antes de agrupar; HAVING filtra grupos depois de agregar. "Convidados do casamento 1" é WHERE; "mesas com mais de 90%" é HAVING (porque o % só existe depois do COUNT). E a regra do GROUP BY: toda coluna do SELECT que não está dentro de função de agregação precisa estar no GROUP BY (o MySQL 8 reclama se não estiver, e ele está certo).

Subquery quando o filtro depende de outro resultado: WHERE acompanhantes > (SELECT AVG(acompanhantes) FROM convidado) (convidados acima da média). Subquery no FROM cria uma "tabela temporária" pra agregar em cima de agregação. São ferramentas de canivete: use quando o JOIN não expressa a pergunta.

🎯Foco de prova

🎯 O dashboard nasce no SQL, e essa seção É o dashboard. Cada query acima corresponde a um número ou gráfico da tela do módulo 07. Quem sai daqui com essas seis queries no músculo monta o dashboard do Módulo C em minutos, porque a parte difícil (a pergunta certa em SQL) já está pronta. O front só pinta o resultado.


16. Views e índices: o tunning que a UC3 pede

16.1 View: consulta com nome

View é uma consulta salva que se usa como tabela:

CREATE VIEW vw_ocupacao_mesa AS
SELECT m.id, m.casamento_id, m.numero, m.capacidade,
       COUNT(c.id) AS ocupacao,
       ROUND(COUNT(c.id) / m.capacidade * 100) AS pct
FROM mesa m
LEFT JOIN convidado c ON c.mesa_id = m.id
GROUP BY m.id, m.casamento_id, m.numero, m.capacidade;

-- E agora o dashboard vira consulta simples:
SELECT * FROM vw_ocupacao_mesa WHERE casamento_id = 1 AND pct >= 90;

O que a view compra: a query cabeluda mora num lugar só (mudou a regra, muda na view, todo consumidor ganha de graça), e o código da API fica limpo. O que ela NÃO compra: velocidade (a view roda a consulta por baixo toda vez; não é cache). Na prova, view no dashboard é organização que a banca vê e a UC3 pede nominalmente.

16.2 Índice: o atalho de leitura

Sem índice, buscar WHERE email = 'ana@...' obriga o banco a ler a tabela INTEIRA (full scan). Índice é uma estrutura ordenada ao lado da tabela (uma árvore B-tree) que leva direto às linhas certas, como o índice remissivo de um livro.

O que já vem indexado sem você pedir: PK (sempre), UNIQUE (sempre) e, no InnoDB, toda FK declarada ganha índice automaticamente. Ou seja: o schema da seção 10 já nasce bem indexado pros JOINs.

Quando criar na mão: coluna que aparece com frequência em WHERE, ORDER BY ou JOIN e que não é PK/UNIQUE/FK. No Wedding Pass, o candidato real é a busca por nome:

CREATE INDEX idx_convidado_nome ON convidado(nome);

Índice composto (mais de uma coluna) segue a regra da esquerda: um índice em (casamento_id, nome) serve pra filtrar por casamento_id sozinho e por casamento_id + nome, mas NÃO serve pra filtrar só por nome (o MySQL lê o índice da esquerda pra direita e não pula coluna). Por isso a ordem das colunas importa: a coluna de igualdade mais usada vai primeiro.

O custo que ninguém conta: todo índice desacelera escrita (cada INSERT/UPDATE atualiza a tabela E cada índice) e ocupa espaço. Índice em tudo é tão errado quanto índice em nada.

E a ferramenta de conferência: EXPLAIN SELECT ... mostra o plano do banco. Na coluna type, ALL significa full scan (sem índice); ref/range significa que o índice entrou. Não precisa dominar EXPLAIN pra prova; precisa saber que ele existe e o que ALL denuncia.

🛠️Gambiarra boa

🛠️ Gambiarra boa (a política de índice da prova): confia nos automáticos (PK, UNIQUE, FK) e adiciona no máximo um ou dois na mão, nas colunas de busca do usuário (nome, código). Nessa escala de dados a diferença de performance é invisível, mas a frase "criei índice no nome porque é o campo de busca da recepção" na apresentação mostra que você conhece a ferramenta e o critério. É dos pontos de tunning mais baratos do CIS.


17. Transações: tudo ou nada

Transação agrupa operações numa unidade atômica (o A do ACID): ou tudo confirma, ou tudo desfaz.

START TRANSACTION;

INSERT INTO convidado (casamento_id, nome, email)
VALUES (1, 'Duda Reis', 'duda@email.com');

INSERT INTO convite (convidado_id, codigo)
VALUES (LAST_INSERT_ID(), 'K2M8XP4Q');   -- LAST_INSERT_ID(): o id do insert acima

COMMIT;      -- confirma os dois juntos
-- ROLLBACK; -- ou desfaz os dois juntos

Sem transação, se o segundo INSERT falhar (código duplicado, por exemplo), o convidado fica criado sem convite: estado meio-termo que ninguém pediu. Com transação, falhou qualquer parte → ROLLBACK → banco como se nada tivesse acontecido.

Quando usar na prova: toda operação que escreve em mais de uma tabela e precisa ser coerente. Criar convidado + convite. Registrar check-in + qualquer atualização de contagem. No módulo 04 a transação reaparece dentro do código da API (nas duas stacks), e no módulo 07 ela se junta com o UNIQUE do check-in pra fechar a solução da condição de corrida (dois check-ins simultâneos do mesmo código: o isolamento + a constraint garantem que só um passa).

🎯Foco de prova

🎯 Vocabulário de nível 3: "essa operação é transacional porque escreve em duas tabelas" é o tipo de frase que, dita naturalmente na apresentação, vale mais que dez minutos de tela bonita. Banca técnica reconhece na hora quem entende atomicidade.


18. Backup, restore, import e export: a política de recuperação

18.1 Dump e restore (a dupla que salva provas)

# Backup (export): o banco inteiro vira um arquivo .sql com CREATEs e INSERTs
mysqldump -u root -p wedding_pass > backup_wedding_2026-08-14.sql

# Restore (import): o arquivo recria tudo
mysql -u root -p wedding_pass < backup_wedding_2026-08-14.sql

O mysqldump gera um script SQL completo: estrutura + dados. Serve de backup, de transporte entre máquinas (a dupla treina em casa e restaura no Senac) e de entrega (se a prova pedir "exporte o banco", é isso).

18.2 A política de recuperação (o conceito que a UC3 pede)

Política de recuperação responde três perguntas: o que copiar (o banco da prova), quando copiar (nos marcos: terminou o schema, terminou o seed, terminou uma feature grande) e como voltar (restore testado, não só backup guardado). Backup que nunca foi restaurado é promessa, não política.

Na competição, a política é simples e real: mysqldump ao fechar cada etapa do Módulo B + o schema.sql + seed.sql versionados no Git. Qualquer desastre (drop errado, UPDATE sem WHERE, máquina travou) se resolve em menos de um minuto.

Import/export de dados avulsos: SELECT ... INTO OUTFILE exporta CSV, LOAD DATA INFILE importa CSV em massa (é a "carga massiva" citada na UC9). Bom saber que existem; na prova, o caminho normal é o dump.

🛠️Gambiarra boa

🛠️ Gambiarra boa: o dump é a máquina do tempo. Vai fazer uma mexida grande e arriscada no banco no meio da prova? 15 segundos: mysqldump antes. Deu ruim? Restaura e ninguém soube. Essa disciplina tira o medo de mexer, e quem não tem medo de mexer trabalha mais rápido. (E o commit no Git faz o mesmo pelo código: módulo 02.)

18.3 Senha no banco: hash, nunca texto

🎯Foco de prova

🎯 Avaliação objetiva direta (sim/não): a banca abre a tabela de usuários e olha. Ou tem hash, ou é zero nesse item. E é zero dos dolorosos, porque custava duas linhas de código.

⚠️ Pega-ratão: senha em texto puro "só pra testar" e esquecer de trocar. Na correria acontece. A vacina: o seed já cria o usuário com a senha hasheada desde o primeiro dia, e ninguém nunca insere usuário na mão.


19. Seed: povoar o banco com massa realista

Seed é o script que carrega os dados iniciais, de forma automatizada e repetível. O descritivo pede, e a demo depende dele.

O que um seed de nível 3 tem:

-- seed.sql (trecho): multi-row INSERT é a forma
INSERT INTO usuario (nome, email, senha_hash, perfil) VALUES
  ('Admin',   'admin@wp.com',   '$2b$10$hash...aqui', 'admin'),
  ('Marina',  'marina@wp.com',  '$2b$10$hash...aqui', 'organizador'),
  ('Portaria','porta@wp.com',   '$2b$10$hash...aqui', 'recepcao');

INSERT INTO convidado (casamento_id, mesa_id, nome, email, acompanhantes) VALUES
  (1, 1, 'Ana Souza',  'ana@email.com',  1),
  (1, 1, 'Bruno Lima', 'bruno@email.com',0),
  (1, NULL, 'Carla Dias','carla@email.com',2),
  -- ... 30+ linhas, estados variados
  (1, 3, 'Zeca Prado', 'zeca@email.com', 0);

Pra gerar massa grande sem digitar: o INSERT ... SELECT da seção 12 gera os convites de todos de uma vez, e um loop pequeno na aplicação (ou uma planilha que monta as linhas de INSERT com fórmula de CONCAT) gera os convidados. Essa é a "carga massiva" da UC9 no nosso tamanho.

🛠️Gambiarra boa

🛠️ Gambiarra boa: seed é o palco pronto. Chega no Módulo C com o banco cheio e variado, e o dashboard nasce com números de verdade na primeira renderização. Chega no Módulo D (apresentação) com uma história pra contar em cima de dados que parecem reais. Seed bom se prepara no Módulo B pensando na demo do D: é o mesmo arquivo trabalhando três módulos.


20. Autoavaliação do módulo

  1. Quando um banco não relacional seria melhor escolha que o relacional? E por que o sistema da prova é o caso oposto?
  2. Explica a diferença entre modelo conceitual, lógico e físico, e o que acontece com um N:N em cada nível.
  3. No sistema da escola, por que a nota é atributo da matrícula e não do aluno? Acha um exemplo do mesmo padrão no Wedding Pass.
  4. Qual a diferença entre chave candidata, primária e substituta? Por que o CPF não deve ser PK?
  5. Monta de memória a "tabelona" errada e mostra qual forma normal cada problema dela viola.
  6. O que são as três anomalias (inserção, atualização, exclusão)? Dá um exemplo de cada.
  7. Ligação circular: quais os três tipos, qual deles é legítimo e como se quebra um ciclo entre duas tabelas?
  8. Com o banco vazio, que pergunta você faz pra detectar um nó de obrigatoriedade mútua no modelo?
  9. Justifica a regra de deleção de cada FK do Wedding Pass (CASCADE, SET NULL ou RESTRICT) como se estivesse na apresentação.
  10. Por que UNIQUE (convidado_id) na tabela de check-in é o bloqueio de dupla entrada mais confiável do sistema?
  11. Por que dinheiro é DECIMAL e nunca FLOAT? E por que CPF é VARCHAR e nunca INT?
  12. Qual a diferença entre WHERE e HAVING? Escreve a query das mesas acima de 90%.
  13. Escreve, sem olhar, a query "convidados sem convite" (LEFT JOIN + IS NULL) e explica por que INNER JOIN não resolveria.
  14. O que uma view compra e o que ela não compra? E qual a regra da esquerda do índice composto?
  15. Quando uma operação precisa de transação? Dá dois exemplos do Wedding Pass.
  16. Descreve a política de recuperação da prova: o que, quando e como voltar.
  17. Lista quatro características de um seed de nível 3.

Referências do módulo

Do plano de curso (UC3): - MySQL 8.0 Reference Manual — downloads.mysql.com/docs/refman-8.0-en.pdf (a fonte da verdade do dialeto da prova; treinar scanning nela é módulo 09 acontecendo) - ELMASRI, R.; NAVATHE, S. Sistemas de banco de dados. Pearson. (modelagem, formas normais e ACID a fundo) - TEOREY, T. et al. Projeto e modelagem de banco de dados. Elsevier. (o caminho conceitual → lógico → físico)

Do mercado: - Multiple-Column Indexes — MySQL Reference Manual (a regra da esquerda, da fonte) - Composite Indexes — MySQL for Developers, PlanetScale (curso gratuito, o melhor material prático de índice que existe) - Modelagem de Bancos Relacionais: boas práticas — DevMedia (inclui a armadilha da obrigatoriedade dos dois lados) - Modelagem de Bancos de Dados sem Segredos — Albert Eije, Medium (série em português, boa pra revisão)

💡Nota

Fundação de dados fechada, e fechada com porquês. Agora a gente constrói a camada que serve esses dados pro mundo, nas duas stacks: 04 — Back-end e API RESTful.

Seletiva 26 · Desenvolvimento de Sistemas · Senac RS