← Voltar pro guia
Estudo

Guia por aula

O que cada slide ensinou, organizado por aula. Para cada exercício há um "Como resolver" com dicas: onde procurar e o que testar. A resposta você escreve e roda no Workbench.

MySQL / MySQL Workbench Aulas 01 a 07 8 exercícios
0 de 8 exercícios feitos
01

Apresentação e revisão

03/08 - 04/08/2026

Combinados da disciplina, calendário e a base que vem do semestre anterior. Os slides da revisão só listam os tópicos, então a lista abaixo serve de roteiro de autoestudo.

  • Dinâmica: explicação (teórica, prática e com participação) e no mínimo 1 exercício por aula. Exercício de hoje vence às 23:59 do 2º domingo.
  • Nota de cada bimestre: Avaliação (0-5) + Atividades (0-2) + APEX (0-1) + Avaliação Integrativa (0-2). A substitutiva (0-10) troca a menor nota de bimestre.
  • Ferramentas: MySQL Server + MySQL Workbench, MongoDB + Compass (NoSQL, no 2º bimestre), Visual Paradigm / Draw.io para modelagem.
  • Revisão que precisa estar na ponta da língua: análise de requisitos, modelo conceitual (MER/DER), modelo lógico, normalização e modelo físico.
AulaDatasAssunto
01-0103/08 - 04/08Apresentação + revisão
02-0210/08 - 11/08Intersect, except, case, having, with
03-0317/08 - 18/08Subquery + índice
04-0424/08 - 25/08View
05-0531/08 - 01/09Trigger + function
06-0608/09 e 14/09Stored procedure
07-0821/09 - 22/09Revisão + APEX 1º bimestre
08-0928/09 - 29/09Avaliação do 1º bimestre
09-1005/10 - 06/10Devolutiva + transação, concorrência e controle
10 a 1119/10 - 27/10NoSQL (MongoDB)

Exercício 1 — modelos do e-commerce Turma A e B: 16/08

  1. A partir do texto crie o modelo conceitual, o modelo lógico e o modelo lógico normalizado.
  2. Texto: cada cliente (nome, e-mail, senha, endereço de entrega) pode fazer compras de vários produtos. Cada produto pertence a uma categoria (nome e descrição) e tem nome, preço e descrição.
  3. Entregar os diagramas em PDF ou JPG.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. Substantivos = entidades. Sublinhe no texto: cliente, produto, categoria e compra. Tudo que só descreve alguém (nome, e-mail, preço) vira atributo.
  2. Verbos = relacionamentos. Leia "cliente faz compras de produtos" e pergunte dos dois lados: um cliente faz quantas compras? uma compra tem quantos produtos? um produto aparece em quantas compras?
  3. Categoria é 1:N com produto (um produto pertence a uma categoria; uma categoria tem vários produtos).
  4. Lógico: toda relação 1:N gera uma FK do lado N; uma relação N:N gera uma tabela nova com as duas chaves. Descubra qual das relações acima é N:N.
  5. Normalizado: confira se o endereço de entrega e o preço aparecem repetidos onde não deveriam. Pergunte: o preço do produto hoje muda o valor de compras antigas?
  6. Teste: para cada linha do diagrama consiga apontar a coluna FK que a representa.

Exercício 2 — a tabela produto está na 3FN? Turma A e B: 16/08

  1. Tabela produto: id (PK), nome, categoria, preco_compra, preco_venda, lucro, fornecedor, cidade_fornecedor.
  2. Responda se está na 3FN; se não, justifique e altere para atender a 3FN.
  3. Entregar PDF ou JPG da normalização.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. Regra da 3FN: cada coluna não-chave deve depender só da chave, e não de outra coluna não-chave.
  2. Pergunte de cada coluna: dá para calcular a partir de outras? (olhe lucro).
  3. Pergunte: quem determina cidade_fornecedor, o produto ou o fornecedor? Isso é uma dependência transitiva.
  4. Pergunte: a categoria repete texto em vários produtos? Vale uma tabela própria?
  5. Justifique citando o nome da coluna e a coluna da qual ela depende, e desenhe a nova estrutura com PK e FK.

Exercício 3 — aluno, disciplina e aluno_disciplina Turma A e B: 16/08

  1. Tabelas: aluno (id, nome VARCHAR(250), data_nascimento DATE, sexo VARCHAR(1), formado VARCHAR(1)), disciplina (id, nome VARCHAR(50), semestre INT) e aluno_disciplina (id_aluno, id_disciplina).
  2. 1) criar as tabelas; 2) mín. 3 alunos, 5 disciplinas e 9 registros em aluno_disciplina; 3) todos os alunos; 4) alunos do sexo M; 5) nome e formado das alunas F; 6) mudar o nome do aluno de id 3 para "Nome alterado".
  3. 7) novo aluno sem disciplina; 8) nome do aluno e da disciplina só dos relacionados; 9) todos os alunos (id, nome, nascimento) e, se houver, id e nome da disciplina.
  4. 10) nova disciplina sem aluno; 11) todos os registros de aluno e de disciplina, relacionados ou não; 12) disciplinas de semestre ímpar; 13) id e nome das disciplinas cujo nome tem "de"; 14) alunos que cursam disciplina do 1º, 3º, 5º ou 7º semestre.
  5. Entregar o arquivo .sql.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. 1-2: crie primeiro aluno e disciplina; a aluno_disciplina tem duas FKs e pode ter PK composta. Insira os pais antes dos filhos (senão erro 1452).
  2. 3-5 e 12-13: SELECT com WHERE. Para "contém a palavra" procure LIKE e o coringa %. Para semestre ímpar use o resto da divisão (aula 02, slide do %).
  3. 6: UPDATE ... SET ... WHERE id = 3. Antes de rodar, faça o mesmo WHERE em um SELECT para ver o que será afetado.
  4. 8: só os relacionados = INNER JOIN passando pela tabela do meio.
  5. 9: todos os alunos, mesmo sem disciplina = LEFT JOIN. Confira: o aluno do item 7 deve aparecer com disciplina vazia (NULL).
  6. 11: todos de ambos os lados = simule o FULL JOIN (o slide da aula 02 diz com qual comando).
  7. 14: IN (1,3,5,7) sobre o semestre e um JOIN. Cuidado com aluno repetido: talvez precise de DISTINCT.
02

UNION, INTERSECT, EXCEPT, CASE, HAVING e WITH

10/08 - 11/08/2026

Como combinar consultas, criar colunas calculadas com condição, filtrar grupos e reaproveitar o resultado de uma busca.

Query

  • Cada comando SQL é individual: SELECT busca em 1 ou + tabelas e exibe, INSERT, UPDATE e DELETE mexem em 1 tabela e acabam ali.
  • Para combinar comandos existem UNION, INTERSECT e EXCEPT.

UNION, INTERSECT, EXCEPT

  • Forma geral: <SELECT> UNION ou INTERSECT ou EXCEPT <SELECT>. Os dois SELECT têm o mesmo número de colunas.
  • UNION: junta tudo e ignora repetidos (usado para simular o FULL JOIN). UNION ALL: junta tudo, com repetidos.
  • INTERSECT: valores comuns aos dois. EXCEPT: o que está no 1º e não está no 2º.
Slides: tabelas numero (1 a 10) e numero_par (2 a 10)
SELECT valor FROM numero
UNION
SELECT valor FROM numero_par;      -- 1..10 sem repetir

SELECT valor FROM numero
UNION ALL
SELECT valor FROM numero_par;      -- 15 linhas (2,4,6,8,10 repetem)

SELECT valor FROM numero
INTERSECT
SELECT valor FROM numero_par;      -- 2,4,6,8,10

SELECT valor FROM numero
EXCEPT
SELECT valor FROM numero_par;      -- 1,3,5,7,9

CASE

  • Funciona como o if/else ou switch de programação: CASE WHEN <condição> THEN <resultado> ... ELSE <resultado> END.
  • Dê nome à coluna com END AS 'nome'. O WHEN é avaliado de cima para baixo e vale o primeiro que for verdadeiro.
  • Dá para aninhar: ELSE CASE WHEN ... END.
SELECT valor,
  CASE
    WHEN valor % 5 = 0 THEN 'Múltiplo de 5'
    WHEN valor % 3 = 0 THEN 'Múltiplo de 3'
    WHEN valor % 2 = 0 THEN 'Par'
    ELSE 'Ímpar'
  END AS 'Análise'
FROM numero;

-- aninhado: primeiro par/ímpar, depois só o ímpar é analisado
SELECT valor,
  CASE
    WHEN valor % 2 = 0 THEN 'Par'
    ELSE CASE
      WHEN valor % 5 = 0 THEN 'Múltiplo de 5'
      WHEN valor % 3 = 0 THEN 'Múltiplo de 3'
      ELSE 'Ímpar'
    END
  END AS 'Análise'
FROM numero;

GROUP BY, HAVING e ORDER BY

  • GROUP BY agrupa o resultado e precisa de uma função de agregação: COUNT, SUM, AVG, MAX, MIN.
  • ORDER BY qtd DESC ordena pelo apelido, do maior para o menor.
  • Filtrar o resultado do grupo com WHERE não funciona: com o apelido dá erro 1054 (Unknown column) e com a função direta dá erro 1111 (Invalid use of group function).
  • HAVING é o WHERE dos agrupamentos. Pode existir WHERE e HAVING na mesma consulta.
Tabela exercicio (numero, id_aluno, nota)
SELECT numero, COUNT(numero) AS qtd, AVG(nota) AS media
FROM exercicio
GROUP BY numero
HAVING media > 6
ORDER BY qtd DESC;

WITH (CTE)

  • O resultado de uma busca é usado por outro comando. Funciona como uma tabela temporária com nome: WITH nome(colunas) AS (SELECT ...).
  • Serve para o SELECT, mas também para INSERT, UPDATE e DELETE.
WITH info_temp(numero, quantidade, media) AS (
  SELECT numero, COUNT(numero), AVG(nota) FROM exercicio GROUP BY numero)
SELECT numero, quantidade, media
FROM info_temp
WHERE media > 6
ORDER BY quantidade DESC;

-- WITH dentro do INSERT
INSERT INTO calculado(exercicio, quantidade, media)
WITH info_temp(numero, quantidade, media) AS (
  SELECT numero, COUNT(numero), AVG(nota) FROM exercicio GROUP BY numero)
SELECT numero, quantidade, media FROM info_temp WHERE media > 6;

-- WITH antes do UPDATE (com JOIN) e do DELETE
WITH info_temp(numero, quantidade, media) AS (
  SELECT numero, COUNT(numero), AVG(nota) FROM exercicio GROUP BY numero)
UPDATE calculado
INNER JOIN info_temp ON info_temp.numero = calculado.exercicio
SET calculado.quantidade = info_temp.quantidade, calculado.media = info_temp.media;

Exercício 4 — pai e filho Turma A e B: 23/08

  1. Tabelas: pai (id, nome VARCHAR(100)) e filho (id, nome VARCHAR(100), id_pai). Relação 1:N.
  2. 1) criar as tabelas; 2) mín. 5 registros em cada, com nomes iguais e diferentes; 3) nome do pai e nome do filho em 2 colunas; 4) em 1 coluna só, o nome de todos os pais e filhos em ordem ascendente; 5) o mesmo sem duplicidade.
  3. 6) nomes comuns entre pais e filhos; 7) nomes de pais que não são nome de algum filho; 8) todos os nomes de pais e filhos, mas o nome do pai não pode ser de algum filho e vice-versa.
  4. 9) nome do pai e quantidade de filhos; 10) nome e quantidade de pessoas (pais + filhos) com esse nome; 11) pai com 2 ou mais filhos; 12) repetir o 11 com WITH.
  5. 13) criar a tabela pai_filho (nome do pai e nome do filho); 14) usando WITH, gravar nela todas as informações de pai e filho.
  6. Entregar o arquivo .sql.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. 2: para testar depois, insira nomes que existam nas duas tabelas (ex.: um pai e um filho com o mesmo nome).
  2. 3: JOIN entre pai e filho pela FK. Use apelidos de coluna diferentes.
  3. 4: 1 coluna só empilhando resultados = UNION ALL (mantém repetidos) e ORDER BY no final.
  4. 5: o mesmo, mas sem duplicidade = troque UNION ALL pelo comando que remove repetidos.
  5. 6 e 7: comuns = INTERSECT; "está no 1º e não no 2º" = EXCEPT.
  6. 8: é a diferença simétrica: (pais EXCEPT filhos) juntado com (filhos EXCEPT pais). Confira: nenhum nome comum aparece.
  7. 9-11: LEFT JOIN + COUNT + GROUP BY. Filtro sobre a contagem só com HAVING. Teste o erro 1111 de propósito para nunca esquecer.
  8. 10: una os nomes das duas tabelas (aqui repetidos contam!) e agrupe por nome.
  9. 12: guarde o item 9 dentro de um WITH e filtre a quantidade no WHERE de fora.
  10. 13-14: CREATE TABLE e depois INSERT INTO ... WITH ... SELECT (slide de WITH no INSERT).
03

Subquery e índice

17/08 - 18/08/2026

Uma consulta dentro de outra, em todos os comandos, e como acelerar buscas com índices.

  • Subquery é uma query dentro de outra query: um SELECT dentro de outro comando.
  • Tabelas dos slides: numero_par, numero_impar e numero_texto (valor, texto em português, texto_ingles).

Subquery no SELECT (5 lugares)

#Onde entraExemplo do slide
1Como coluna do selectSELECT valor, (SELECT texto FROM numero_texto WHERE valor = numero_par.valor) AS 'Texto Português' FROM numero_par
2Como filtro no WHEREWHERE valor IN (SELECT valor FROM numero_par)
3Como uma tabela (exige apelido)FROM (SELECT valor FROM numero_impar ORDER BY valor) AS tabela
4Como filtro no HAVINGGROUP BY valor HAVING valor IN (SELECT valor FROM numero_impar)
5Como campo de ordenaçãoORDER BY (SELECT texto FROM numero_texto WHERE valor = numero_par.valor)
  • Se a subquery usada como coluna devolver 2 colunas (texto, texto_ingles) dá erro 1241 (Operand should contain 1 column).

Subquery no INSERT, UPDATE e DELETE

-- INSERT: como fonte dos dados
INSERT INTO numero_par_texto(valor, texto, texto_ingles)
SELECT valor, texto, texto_ingles FROM numero_texto WHERE valor % 2 = 0;

-- INSERT: como parte da informação
INSERT INTO numero_par_texto(valor, texto, texto_ingles)
VALUES ((SELECT (COUNT(valor)+1)*2 FROM numero_par), '', '');

-- UPDATE: como nova informação
UPDATE numero_par_texto
SET texto = (SELECT texto_ingles FROM numero_texto WHERE valor = numero_par_texto.valor);

-- UPDATE e DELETE: como filtro no WHERE
UPDATE numero_par_texto SET texto_ingles = 'Texto em inglês'
WHERE valor IN (SELECT valor FROM numero_texto);

DELETE FROM numero_par_texto
WHERE valor IN (SELECT valor FROM numero_texto);
!

O que não pode: no INSERT, UPDATE e DELETE a subquery não pode usar a mesma tabela que está sendo alterada.

Índice e EXPLAIN ANALYZE

  • Índice é como o índice de um livro: acha o assunto sem folhear página por página. Serve para otimizar buscas.
  • Use nas colunas mais usadas em busca. Cuidado: índice em tudo deixa inserir, atualizar e deletar mais custoso.
  • EXPLAIN ANALYZE explica como o comando roda e quanto tempo levou, para ver (ou não) o ganho do índice.
  • Sem índice aparece Table scan; com índice, Covering index scan ou range scan.
  • Com 100 linhas o ganho é mínimo (0,0054 ms). Com 121.643 linhas, WHERE valor > 100 AND valor < 1000 o índice economizou cerca de 28 ms no total: o banco leu só as 449 linhas do intervalo em vez de varrer todas.
CREATE INDEX index_numero_par ON numero_par(valor);
DROP INDEX index_numero_par ON numero_par;

EXPLAIN ANALYZE SELECT valor FROM numero_par WHERE valor > 100 AND valor < 1000;

Exercício 5 — pessoa e automóvel Turma A e B: 30/08

  1. Tabelas: pessoa (id, nome VARCHAR(45), profissao VARCHAR(45), cnh VARCHAR(1)) e automovel (id, carro_moto VARCHAR(1), modelo VARCHAR(45), preco DECIMAL, id_pessoa). Relação 1:N.
  2. 1) criar as tabelas; 2) mín. 5 registros em cada, com pessoas sem automóvel e com mais de 1; 3) todas as informações de pessoa, mas mostrando 'Sim' ou 'Não' no lugar de 'S' ou 'N' na cnh; 4) nome da pessoa e quantidade de automóveis.
  3. 5) nome de todas as pessoas e, se tiver automóvel, se é 'Moto' ou 'Carro' e o modelo; 6) criar pessoa_automovel (nome da pessoa, modelo e preço); 7) com subquery, inserir nela só as pessoas que possuem automóveis; 8) nome da pessoa e o preço total dos automóveis, da garagem mais cara para a mais barata.
  4. 9) atualizar o preço de algum automóvel; 10) com subquery, atualizar na nova tabela o preço de todos os registros desse automóvel; 11) com subquery, buscar na nova tabela os automóveis de quem é 'Desenvolvedor' ou 'Médico'; 12) índice na nova tabela pela coluna modelo; 13) com nova tabela e pessoa, nome e profissão de quem não tem automóvel.
  5. Entregar o arquivo .sql.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. 2: para testar depois, deixe uma pessoa com 0 carros e outra com 2 ou mais.
  2. 3: CASE WHEN cnh = 'S' THEN ... ELSE ... END AS ... (aula 02).
  3. 4: LEFT JOIN + COUNT(automovel.id) + GROUP BY. Confira: quem não tem carro deve aparecer com 0. Pergunte por que COUNT(*) daria 1.
  4. 5: LEFT JOIN de novo, com um CASE sobre carro_moto ('C'/'M' ou como você gravou).
  5. 6-7: INSERT INTO ... SELECT onde o filtro é uma subquery (slide Subquery - Insert 1 e Select 2).
  6. 8: SUM(preco) por pessoa e ORDER BY no total, decrescente.
  7. 9-10: primeiro o UPDATE simples; depois UPDATE com subquery no SET (slide Update 1). Lembre que não pode usar a própria tabela na subquery.
  8. 11: subquery com IN sobre o resultado de pessoa filtrado por profissão (slide Select 2).
  9. 12-13: CREATE INDEX na coluna modelo; o item 13 é um "quem não está em": NOT EXISTS ou NOT IN (cuidado com NULL).
04

View

24/08 - 25/08/2026

Uma "tabela" virtual definida por um SELECT, útil para esconder colunas e guardar consultas frequentes.

  • View significa visão: um jeito de olhar as informações. Parece uma tabela, mas não guarda os dados (não copia nada da tabela original).
  • Para que serve: (1) dar acesso só a algumas colunas (permissão é por tabela, não por coluna) e (2) guardar uma consulta complicada e frequente, como um relatório.
  • Se entra um novo aluno na tabela original, ele aparece na view se estiver no resultado da consulta que monta a view.
  • A view pode ter INNER JOIN, LEFT JOIN, RIGHT JOIN, UNION, INTERSECT e EXCEPT: o SELECT é quem decide.
CREATE VIEW view_aluno AS
SELECT nome, ra
FROM aluno
ORDER BY nome;

DESC view_aluno;
SELECT * FROM view_aluno;
DROP VIEW view_aluno;

A view aceita UPDATE, INSERT e DELETE?

  • Depende da permissão. Numa view de uma tabela só e SELECT simples, sim, e mexe na tabela original.
  • UPDATE view_aluno SET ra = '999' WHERE nome = 'Danilo' mudou o RA na tabela aluno.
  • INSERT na view funciona, mas avisa 1423 (Field of view ... doesn't have a default value) porque as outras colunas são NOT NULL. Dependendo da política pode bloquear.
  • DELETE na view também apaga da tabela original.
  • View com UNION (mais de uma fonte) é só leitura: UPDATE dá erro 1288 (target table ... is not updatable).

Exercício 6 — filme e personagem Turma A e B: 06/09

  1. Tabelas: filme (id, titulo, tipo, diretor, ano_lancamento) e personagem (id, nome, especie, nivel_importancia, id_filme). Relação 1:N.
  2. 1) criar; 2) mín. 3 filmes e 10 personagens; 3) nome do filme e quantidade de personagens; 4) nome do diretor e quantidade de filmes dirigidos por ele; 5) nome, espécie e nível de importância de todos os personagens, ordenando pelo ano de lançamento do filme e depois do mais para o menos importante.
  3. 6) título do filme e nome dos personagens dos filmes de ação ou drama; 7) em 1 coluna só, o nome do diretor e dos personagens em ordem alfabética; 8) view view_filme (título e tipo); 9) buscar nela tudo ordenado pelo título; 10) view view_personagem (nome, espécie e id do filme); 11) com ela, nome do personagem e título do filme.
  4. 12) view view_filme_personagem (título, tipo, diretor, ano e nome do personagem) só dos filmes de ano de lançamento par; 13) título dos filmes de ano não par, sem usar ano_lancamento no filtro; 14) título, tipo, diretor, ano e personagem que não estão na view_filme_personagem.
  5. Entregar o arquivo .sql.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. 2: varie os tipos (ação, drama, comédia) e os anos (pares e ímpares) para os testes fazerem sentido.
  2. 3-4: GROUP BY com COUNT. No 4, agrupe pelo diretor.
  3. 5: JOIN e ORDER BY ano_lancamento, nivel_importancia DESC.
  4. 6: WHERE tipo IN (...).
  5. 7: 1 coluna = UNION entre diretores e nomes de personagens, ordenando no fim.
  6. 8-11: CREATE VIEW nome AS SELECT .... Teste: DESC da view e um SELECT normal nela. O item 11 é um JOIN em que a view faz o papel da tabela.
  7. 12: o filtro "ano é par" está na aula 02 (resto da divisão por 2).
  8. 13: você não pode filtrar por ano_lancamento. Pense: todos os filmes menos os que estão na view de pares. Qual comando de combinar faz "menos"? (Se a view não tiver o id do filme, inclua.)
  9. 14: de novo "o que não está na view": compare com NOT IN/NOT EXISTS ou EXCEPT sobre as mesmas colunas.
05

Trigger e function

31/08 - 01/09/2026

Ações automáticas disparadas por INSERT, UPDATE e DELETE, e funções próprias que devolvem um valor.

Trigger (gatilho)

  • Quando acontece um INSERT, UPDATE ou DELETE, o trigger executa uma ação automática. Pode ser antes ou depois de cada um dos três: 6 combinações.
  • NEW é a linha nova (existe no INSERT e no UPDATE); OLD é a linha antiga (existe no UPDATE e no DELETE).
  • Os comandos de dentro têm ;, que é o fim de uma query, então troque o delimitador: DELIMITER $$ ... END $$ ... DELIMITER ;.
  • SIGNAL SQLSTATE '45000' gera uma exceção definida pelo usuário e o MESSAGE_TEXT é a mensagem. Aparece como erro 1644.
  • SHOW TRIGGERS; lista os triggers e SHOW CREATE TRIGGER nome mostra um deles.
BEFORE INSERT: validar
DELIMITER $$
CREATE TRIGGER trigger_before_insert_salario
BEFORE INSERT ON salario
FOR EACH ROW
BEGIN
  IF NEW.remuneracao < 0 THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = 'Remuneração deve ser positiva';
  END IF;
END $$
DELIMITER ;
AFTER INSERT: registrar em outra tabela (log)
DELIMITER $$
CREATE TRIGGER trigger_after_insert_salario
AFTER INSERT ON salario
FOR EACH ROW
BEGIN
  INSERT INTO log_salario(id_salario, nome_funcionario, remuneracao_anterior,
                          remuneracao_nova, datahora, usuario)
  VALUES (NEW.id, NEW.nome_funcionario, 0.0, NEW.remuneracao,
          CURRENT_TIMESTAMP(), USER());
END $$
DELIMITER ;
As outras combinações (resumo)
BEFORE UPDATE:  IF OLD.remuneracao > NEW.remuneracao THEN SIGNAL ... -- não pode diminuir
BEFORE DELETE:  IF OLD.remuneracao < (media/2) OR OLD.remuneracao = maximo THEN SIGNAL ...
AFTER DELETE:   <comandos>   -- só roda se o delete foi permitido
  • Problemas: um trigger que faz INSERT na mesma tabela que o disparou até pode ser criado, mas não executa: erro 1442.
  • Loop entre tabelas: insert na tabela 1 dispara trigger que escreve na 2, que dispara trigger que escreve na 1...
  • Trigger x FK: o DELETE do id 2 passou pelo trigger, mas quem barrou foi a chave estrangeira (erro 1451).

Function

  • Como AVG, COUNT, MAX, MIN, SUM, mas criada por você. Recebe parâmetros e devolve um valor com RETURN.
  • Forma: CREATE FUNCTION nome(parâmetros) RETURNS tipo <DETERMINISTIC> BEGIN ... RETURN ...; END$$.
  • Chama-se dentro de um SELECT, linha a linha. DROP FUNCTION nome apaga.
DELIMITER $$
CREATE FUNCTION verificar_salario_2000(salario DECIMAL(9,2))
RETURNS VARCHAR(100)
DETERMINISTIC
BEGIN
  DECLARE resultado VARCHAR(100);
  IF salario >= 2000 THEN SET resultado = 'Maior ou igual a 2000';
  ELSE SET resultado = 'Menor que 2000';
  END IF;
  RETURN resultado;
END$$
DELIMITER ;

SELECT *, verificar_salario_2000(remuneracao) FROM salario;

Exercício 7 — animal e animal_feroz Turma A e B: 13/09

  1. Tabelas: animal (id, especie VARCHAR(100), nivel_feroz INT, herbivoro_carnivoro VARCHAR(1)) e animal_feroz (id, especie, herbivoro_carnivoro, registros INT).
  2. 1) criar as tabelas; 2) trigger antes de inserir: nivel_feroz entre 1 e 5, senão mensagem de erro; 3) trigger depois de inserir: se nivel_feroz for 4 ou 5, cadastrar em animal_feroz com registros = 1; 4) criar 6 animais (5 válidos e 1 com nivel inválido).
  3. 5) trigger antes de atualizar: se mudar a espécie, não deixar; 6) atualizar um animal mudando a espécie para "espécie inválida"; 7) trigger depois de atualizar: incrementar registros daquele animal se for feroz; 8) atualizar 1 animal feroz e 1 que não é feroz.
  4. 9) trigger antes de deletar: se for herbívoro, não permitir; 10) apagar um herbívoro; 11) trigger depois de deletar: apagar o animal de animal_feroz; 12) apagar um animal de nivel 4 ou 5.
  5. 13) função que recebe a espécie e retorna "Animal feroz!" ou "Animal não é muito feroz!" conforme exista ou não em animal_feroz; 14) todos os animais + a função; 15) função que recebe a letra inicial e retorna "Herbívoro" ou "Carnívoro"; 16) espécie + essa função.
  6. Entregar o arquivo .sql.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. Ordem que evita dor de cabeça: crie a tabela, depois um trigger de cada vez, e teste cada um antes do próximo (o item 4 é o teste dos 2 e 3).
  2. 2: modelo do slide BEFORE INSERT (Remuneração deve ser positiva). Troque a condição para fora do intervalo 1 a 5.
  3. 3: modelo do AFTER INSERT (log_salario), mas com IF NEW.nivel_feroz IN (4,5) em volta.
  4. 4-6: rode um INSERT inválido e leia o erro 1644. No 5 compare OLD.especie com NEW.especie (<>).
  5. 7-8: AFTER UPDATE com UPDATE animal_feroz SET registros = registros + 1 .... Teste: o animal comum não pode mudar animal_feroz.
  6. 9-10: BEFORE DELETE olhando OLD.herbivoro_carnivoro. Confira se o bloqueio veio do trigger (1644) e não de FK (1451).
  7. 11-12: AFTER DELETE que apaga por espécie. Atenção ao erro 1442: só pode mexer em animal_feroz, nunca em animal dentro do trigger de animal.
  8. 13-14: modelo verificar_salario_media (função que consulta outra tabela). Use EXISTS/COUNT em animal_feroz dentro da função.
  9. 15-16: modelo verificar_salario_2000 com IF puro sobre o parâmetro.
06

Stored procedure

08/09 e 14/09/2026

Blocos de comandos guardados no banco, com parâmetros de entrada e saída, laços e erros próprios.

  • Stored procedure é um bloco de comandos guardado no banco e executado com CALL. Diferente da function, não precisa devolver valor.
  • Parâmetros: IN (entra, padrão), OUT (sai, lido depois numa variável @x) e INOUT (entra e sai).
  • Laços: WHILE ... DO ... END WHILE e REPEAT ... UNTIL cond END REPEAT. Erros com SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '...'.
  • Exemplos dos slides: selecionar_1_disciplina, selecionar_prova_com_x_questoes, verificar_resposta, contagem_regressiva (WHILE) e contagem_regressiva_repeat.
DELIMITER $$
CREATE PROCEDURE contar_automoveis(OUT p_total INT)
BEGIN
  SELECT COUNT(*) INTO p_total FROM automovel;
END$$
DELIMITER ;

CALL contar_automoveis(@total);
SELECT @total;

A explicação completa (IN/OUT/INOUT, laços, SIGNAL e erros 1305, 1318 e 1064) está na seção Stored Procedure do guia principal.

Exercício 8 — automóvel e tabuada (stored procedures) Turma A: 27/09 · Turma B: 20/09

  1. Tabelas: automovel (id, carro_moto VARCHAR(1), placa VARCHAR(7), marca, modelo, proprietario VARCHAR(100)) e tabuada (id, n1, n2, resultado).
  2. Procedure sem parâmetros que preenche a tabuada de 1 a 10; procedure com n1 e n2 de entrada e o id de saída; mostrar a chamada e como exibir o resultado.
  3. Procedure com parâmetro de entrada e saída: se o número for primo devolve o próprio número; se não, multiplica pelas ocorrências do número na tabuada. Chamar 3 vezes (primo, resultado de tabuada, não resultado).
  4. Procedure de 7 parâmetros (ação + 6 campos) com INSERIR, ATUALIZAR e EXCLUIR, usando SIGNAL com os textos exatos 'Erro - Placa já cadastrada', 'Erro - Id não existe' e 'Erro - Ação desconhecida'. Chamar 7 vezes (inserir OK e com erro, atualizar OK e com erro, excluir OK e com erro, ação inexistente).
  5. Entregar um único arquivo .sql.
Como resolver (dicas, sem a resposta pronta)

Tente primeiro no MySQL Workbench. Cada dica diz onde procurar e o que conferir; o código mesmo você escreve e testa.

  1. Tabuada: dois WHILE aninhados (n1 de 1 a 10 e n2 de 1 a 10), com um INSERT dentro do interno. Teste: SELECT COUNT(*) deve dar 100.
  2. Com OUT: INSERT e depois SELECT LAST_INSERT_ID() INTO p_id (ou SELECT ... INTO). Chame com @id e depois SELECT @id.
  3. INOUT: primeiro decida se é primo (teste divisores até a raiz do número, FLOOR(SQRT(n))); só se não for primo conte as ocorrências na tabuada.
  4. Chamadas do INOUT: o argumento tem que ser variável (SET @n = 7; CALL ...(@n); SELECT @n;), nunca um número solto.
  5. 7 parâmetros: um IF p_acao = 'INSERIR' ... ELSEIF ... ELSE SIGNAL .... Antes de inserir, verifique a placa; antes de atualizar ou excluir, verifique se o id existe (IF NOT EXISTS (SELECT 1 ...)).
  6. Textos dos erros exatamente como no enunciado, com hífen e acento. Teste as 7 chamadas e confira que os 3 erros aparecem como erro 1644.
  7. Lembre o DELIMITER $$ antes e DELIMITER ; depois de cada procedure (sem isso: erro 1064).
07

Revisão e APEX

21/09 - 22/09/2026

Fechamento do 1º bimestre.

  • Revisão geral: UNION/INTERSECT/EXCEPT, CASE, HAVING, WITH, subqueries, índices, views, triggers, functions e stored procedures.
  • APEX do 1º bimestre (atividade prática de extensão, vale 0-1).
  • A Avaliação do 1º bimestre é na aula 08-09, em 28/09 e 29/09.
  • Depois: transação, concorrência e controle (05/10 e 06/10) e NoSQL com MongoDB (19/10 a 27/10).
✓

Método de estudo: para cada tópico acima, abra o guia, esconda o código, escreva a consulta do zero no Workbench e só depois compare. Errou? O erro cai na tabela de erros do guia principal.