Dia 11 — SQL: alterar, apagar e resumir: PHP e MySQL

Informatica · Conteudo · publicado em 02/10/2026
Dia 11 de 17

SQL: alterar, apagar e resumir

Aula 1

UPDATE e DELETE

UPDATE

UPDATE muda o que já está gravado. A sintaxe é `UPDATE tabela SET coluna =

valor WHERE condição: sem o WHERE`, a mudança vale para todas as linhas da

tabela. String nova de texto precisa de aspas simples; número e NULL vão sem

aspas.

A forma com WHERE

UPDATE <tabela>
SET <coluna> = <valor>, <coluna> = <valor>
WHERE <condicao>;

O WHERE de um UPDATE aceito pode ser tão amplo quanto o de um SELECT.

Atualizar por nome, sem chave, muda todas as linhas do mesmo nome — e o servidor

não avisa.

DELETE

DELETE FROM tabela WHERE condição apaga linha a linha. Sem WHERE, apaga tudo

que está na tabela. A tabela em si continua existindo, com a estrutura intacta, e

o AUTO_INCREMENT não volta atrás.

A forma do DELETE

DELETE FROM <tabela> WHERE <condicao>;

DELETE, TRUNCATE e DROP

São três coisas diferentes e a escolha errada custa caro. DELETE pode ter

WHERE e é reversível dentro de uma transação; TRUNCATE não tem WHERE e

apaga tudo de uma vez; DROP apaga a tabela, com as colunas, os índices e a

chave primária.

As três formas de apagar

DELETE FROM <tabela> WHERE <condicao>;
TRUNCATE TABLE <tabela>;   -- nao aceita WHERE
DROP TABLE <tabela>;       -- apaga a estrutura tambem
ComandoAceita WHEREO que someVolta em transação
DELETESimSó as linhas filtradasSim
TRUNCATENãoTodas as linhasNão
DROP TABLENãoA tabela inteiraNão

Antes de um DELETE largo, a consulta de conferência roda primeiro: o mesmo

WHERE no SELECT mostra quantas linhas caem. rowCount() devolve o número de

linhas afetadas e é a verificação mais barata que existe.

Backup antes de DELETE sem filtro e de DROP TABLE. Não há como desfazer, e

restaurar leva mais tempo do que refazer a consulta com calma.

Exemplo

<?php
declare(strict_types=1);

// UPDATE, DELETE e as diferencas com TRUNCATE e DROP.
// Os UPDATE e DELETE deste arquivo tm WHERE: sem ele a mudanca vale para a tabela toda.

$sql = <<<'SQL'
-- conferindo antes de mexer: o mesmo WHERE em SELECT mostra o que cai
SELECT id, situacao, data_matricula
FROM matriculas
WHERE situacao = 'cancelada' AND data_matricula < '2023-01-01';

-- UPDATE de uma linha, identified pela chave primaria
UPDATE alunos
SET cidade = 'Campinas', atualizado_em = NOW()
WHERE id = 12;

-- UPDATE de varias linhas, com uma condicao que e verdadeira para um grupo
UPDATE cursos
SET valor = valor * 1.10
WHERE carga_horaria >= 60 AND ativo = 1;

-- zerar um campo: NULL e '' sao coisas diferentes
UPDATE alunos SET telefone = NULL WHERE cidade = 'Sao Paulo';

-- DELETE com filtro
DELETE FROM matriculas
WHERE situacao = 'cancelada' AND data_matricula < '2023-01-01';

DELETE FROM alunos WHERE id = 87;

-- TRUNCATE e DROP nao aceitam WHERE: um apaga as linhas, o outro apaga a tabela
-- TRUNCATE TABLE matriculas;
-- DROP TABLE matriculas;
SQL;

echo $sql . "\n";

Saída real

-- conferindo antes de mexer: o mesmo WHERE em SELECT mostra o que cai
SELECT id, situacao, data_matricula
FROM matriculas
WHERE situacao = 'cancelada' AND data_matricula < '2023-01-01';

-- UPDATE de uma linha, identified pela chave primaria
UPDATE alunos
SET cidade = 'Campinas', atualizado_em = NOW()
WHERE id = 12;

-- UPDATE de varias linhas, com uma condicao que e verdadeira para um grupo
UPDATE cursos
SET valor = valor * 1.10
WHERE carga_horaria >= 60 AND ativo = 1;

-- zerar um campo: NULL e '' sao coisas diferentes
UPDATE alunos SET telefone = NULL WHERE cidade = 'Sao Paulo';

-- DELETE com filtro
DELETE FROM matriculas
WHERE situacao = 'cancelada' AND data_matricula < '2023-01-01';

DELETE FROM alunos WHERE id = 87;

-- TRUNCATE e DROP nao aceitam WHERE: um apaga as linhas, o outro apaga a tabela
-- TRUNCATE TABLE matriculas;
-- DROP TABLE matriculas;
Aula 2

ALTER TABLE e índices

Acrescentar, trocar e remover colunas

ALTER TABLE muda a estrutura de uma tabela que já tem dados, e é o comando que

roda na migração de versão. Ele faz uma operação por vez: ADD COLUMN inclui,

MODIFY muda o tipo e o DEFAULT, DROP COLUMN remove.

A sintaxe e as quatro operações

ALTER TABLE <tabela> ADD [COLUMN] <coluna> <tipo> [AFTER <outra>];
ALTER TABLE <tabela> MODIFY [COLUMN] <coluna> <tipo> [NOT NULL] [DEFAULT <valor>];
ALTER TABLE <tabela> DROP [COLUMN] <coluna>;
ALTER TABLE <tabela> RENAME COLUMN <antiga> TO <nova>;

MODIFY reescreve a coluna inteira: o que não estiver na instrução é perdido.

CHANGE faz o mesmo e ainda troca o nome, porque repete o nome antigo antes do

novo — CHANGE COLUMN carga_horaria carga INT NOT NULL.

Índices e restrição em tabela existente

Índice também se cria depois, com CREATE INDEX, e some com DROP INDEX. Índice

composto tem as colunas entre parênteses, e a ordem delas define para qual filtro

ele serve.

Criar índice e acrescentar chave estrangeira

CREATE [UNIQUE] INDEX <nome> ON <tabela> (<colunas>);
ALTER TABLE <tabela> ADD INDEX <nome> (<colunas>);
ALTER TABLE <tabela> ADD CONSTRAINT <nome>
    FOREIGN KEY (<coluna>) REFERENCES <tabela_referida> (<chave>);

ADD CONSTRAINT no ALTER TABLE acrescenta chave estrangeira a uma tabela que

já existe — é a forma de corrigir um modelo antigo sem refazer os dados. Toda

migração é irreversível em parte: DROP COLUMN joga os dados fora, e o caminho

seguro é adicionar a coluna nova, copiar o dado com UPDATE, e só então remover

a antiga.

Exemplo

<?php
declare(strict_types=1);

// ALTER TABLE em uma tabela que ja existe. Cada comando faz uma operacao so,
// e MODIFY reescreve a coluna inteira: o que nao estiver escrito e perdido.

$sql = <<<'SQL'
-- 1) acrescentar coluna: pode ser depois de outra, com AFTER ou logo no fim
ALTER TABLE alunos ADD COLUMN telefone VARCHAR(20) NULL AFTER email;

-- 2) mudar tipo e regra: repete o nome, o tipo e o NOT NULL
ALTER TABLE alunos MODIFY COLUMN nome VARCHAR(160) NOT NULL;

-- 3) renomear coluna, sem perder os dados
ALTER TABLE alunos RENAME COLUMN nascimento TO data_nascimento;

-- 4) CHANGE faz o mesmo, repetindo o tipo: util quando o tipo tambem muda
ALTER TABLE alunos CHANGE COLUMN telefone telefone VARCHAR(30) NULL;

-- 5) remover coluna: o dado e perdido de vez
ALTER TABLE alunos DROP COLUMN telefone;

-- 6) renomear a tabela
ALTER TABLE cursos RENAME TO cursos_oferecidos;

-- 7) indices criados depois da tabela
CREATE UNIQUE INDEX ux_alunos_email ON alunos (email);
CREATE INDEX ix_matriculas_curso_situacao ON matriculas (curso_id, situacao);
ALTER TABLE alunos ADD INDEX ix_alunos_cidade (cidade);
DROP INDEX ix_matriculas_curso_situacao ON matriculas;

-- 8) restricao que faltou, acrescentada sem refazer os dados
ALTER TABLE matriculas
  ADD CONSTRAINT fk_matricula_curso
  FOREIGN KEY (curso_id) REFERENCES cursos (id)
  ON DELETE CASCADE ON UPDATE CASCADE;
SQL;

echo $sql . "\n";

Saída real

-- 1) acrescentar coluna: pode ser depois de outra, com AFTER ou logo no fim
ALTER TABLE alunos ADD COLUMN telefone VARCHAR(20) NULL AFTER email;

-- 2) mudar tipo e regra: repete o nome, o tipo e o NOT NULL
ALTER TABLE alunos MODIFY COLUMN nome VARCHAR(160) NOT NULL;

-- 3) renomear coluna, sem perder os dados
ALTER TABLE alunos RENAME COLUMN nascimento TO data_nascimento;

-- 4) CHANGE faz o mesmo, repetindo o tipo: util quando o tipo tambem muda
ALTER TABLE alunos CHANGE COLUMN telefone telefone VARCHAR(30) NULL;

-- 5) remover coluna: o dado e perdido de vez
ALTER TABLE alunos DROP COLUMN telefone;

-- 6) renomear a tabela
ALTER TABLE cursos RENAME TO cursos_oferecidos;

-- 7) indices criados depois da tabela
CREATE UNIQUE INDEX ux_alunos_email ON alunos (email);
CREATE INDEX ix_matriculas_curso_situacao ON matriculas (curso_id, situacao);
ALTER TABLE alunos ADD INDEX ix_alunos_cidade (cidade);
DROP INDEX ix_matriculas_curso_situacao ON matriculas;

-- 8) restricao que faltou, acrescentada sem refazer os dados
ALTER TABLE matriculas
  ADD CONSTRAINT fk_matricula_curso
  FOREIGN KEY (curso_id) REFERENCES cursos (id)
  ON DELETE CASCADE ON UPDATE CASCADE;
Aula 3

GROUP BY e funções de agregação

GROUP BY

GROUP BY junta as linhas que têm o mesmo valor nas colunas indicadas e trata

cada grupo como uma linha só. O SELECT passa a listar as colunas do grupo e as

funções de agregação; sem GROUP BY, a agregação vale para a tabela inteira.

Agregação com e sem grupo

SELECT COUNT(*), SUM(<coluna>), AVG(<coluna>) FROM <tabela>;
SELECT <coluna_do_grupo>, COUNT(*) FROM <tabela> GROUP BY <coluna_do_grupo>;

Sem GROUP BY a agregação devolve uma linha só, com o total da tabela inteira.

Com GROUP BY, devolve uma linha por valor distinto da coluna indicada.

As funções de agregação

As seis funções agregam muitas linhas em um valor. COUNT(*) conta linhas;

COUNT(coluna) ignora as NULL da coluna. SUM e AVG só fazem sentido em

número, e AVG de coluna com NULL considera só as preenchidas.

As seis funções e a forma de uso

FunçãoDevolve
COUNT(*)Número de linhas do grupo
COUNT(coluna)Linhas com valor na coluna
SUM(coluna)Soma
AVG(coluna)Média
MAX(coluna)Maior valor
MIN(coluna)Menor valor
SELECT SUM(<coluna>), AVG(<coluna>), MAX(<coluna>), MIN(<coluna>) FROM <tabela> GROUP BY <coluna>;

WHERE filtra antes, HAVING filtra depois

É a diferença que mais gera consulta errada da turma. O WHERE age sobre as

linhas da tabela, antes de agrupar: ele descarta aluno por aluno. O HAVING

age sobre os grupos, depois de agregar: ele descarta cidade por cidade. Por

isso WHERE não aceita COUNT(*) e HAVING não aceita coluna que não está no

GROUP BY.

O mesmo filtro, no lugar certo

-- WHERE: filtra a linha antes de agrupar, e por isso nao aceita COUNT
SELECT <coluna>, COUNT(*) FROM <tabela> WHERE <condicao> GROUP BY <coluna>;

-- HAVING: filtra o grupo depois de agregar
SELECT <coluna>, COUNT(*) FROM <tabela> GROUP BY <coluna> HAVING COUNT(*) > <n>;

Quando a condição é sobre a coluna original, ela vai no WHERE e o banco filtra

antes do trabalho de agrupar — mais rápido. Quando a condição é sobre o resultado

da agregação, não há escolha: é HAVING, ou a consulta não roda.

Exemplo

<?php
declare(strict_types=1);

// GROUP BY e agregacao. O ponto que mais gera consulta errada: WHERE vem antes
// de agrupar e filtra linha; HAVING vem depois e filtra grupo.

$sql = <<<'SQL'
-- agregacao sem grupo: vale para a tabela inteira
SELECT COUNT(*) AS total_cursos,
       SUM(valor) AS faturamento,
       AVG(valor) AS ticket_medio,
       MIN(valor) AS menor,
       MAX(valor) AS maior
FROM cursos;

-- COUNT(*) conta linhas; COUNT(coluna) ignora as NULL daquela coluna
SELECT COUNT(*) AS linhas, COUNT(telefone) AS com_telefone FROM alunos;

-- agrupar por uma coluna e medir cada grupo
SELECT cidade, COUNT(*) AS qtd_alunos, AVG(idade) AS media_idade
FROM alunos
WHERE ativo = 1
GROUP BY cidade
ORDER BY qtd_alunos DESC;

-- dois criterios de grupo: alunos matriculados por curso e por situacao
SELECT c.nome AS curso, m.situacao, COUNT(*) AS qtd
FROM matriculas m
JOIN cursos c ON c.id = m.curso_id
GROUP BY c.nome, m.situacao;

-- WHERE: filtra a LINHA antes de agrupar, e por isso nao aceita COUNT
SELECT situacao, COUNT(*) AS qtd
FROM matriculas
WHERE data_matricula >= '2024-01-01'
GROUP BY situacao;

-- HAVING: filtra o GRUPO depois de agregar
SELECT situacao, COUNT(*) AS qtd, AVG(nota_final) AS media
FROM matriculas
GROUP BY situacao
HAVING COUNT(*) > 10 AND AVG(nota_final) >= 7.00
ORDER BY qtd DESC;
SQL;

echo $sql . "\n";

Saída real

-- agregacao sem grupo: vale para a tabela inteira
SELECT COUNT(*) AS total_cursos,
       SUM(valor) AS faturamento,
       AVG(valor) AS ticket_medio,
       MIN(valor) AS menor,
       MAX(valor) AS maior
FROM cursos;

-- COUNT(*) conta linhas; COUNT(coluna) ignora as NULL daquela coluna
SELECT COUNT(*) AS linhas, COUNT(telefone) AS com_telefone FROM alunos;

-- agrupar por uma coluna e medir cada grupo
SELECT cidade, COUNT(*) AS qtd_alunos, AVG(idade) AS media_idade
FROM alunos
WHERE ativo = 1
GROUP BY cidade
ORDER BY qtd_alunos DESC;

-- dois criterios de grupo: alunos matriculados por curso e por situacao
SELECT c.nome AS curso, m.situacao, COUNT(*) AS qtd
FROM matriculas m
JOIN cursos c ON c.id = m.curso_id
GROUP BY c.nome, m.situacao;

-- WHERE: filtra a LINHA antes de agrupar, e por isso nao aceita COUNT
SELECT situacao, COUNT(*) AS qtd
FROM matriculas
WHERE data_matricula >= '2024-01-01'
GROUP BY situacao;

-- HAVING: filtra o GRUPO depois de agregar
SELECT situacao, COUNT(*) AS qtd, AVG(nota_final) AS media
FROM matriculas
GROUP BY situacao
HAVING COUNT(*) > 10 AND AVG(nota_final) >= 7.00
ORDER BY qtd DESC;
Aula 4

Funções de string, data e matemática no SQL

Funções de string

As funções de string recebem texto e devolvem texto ou número. CONCAT junta

vários campos em uma coluna só — com NULL em qualquer argumento, o resultado

inteiro é NULL, e por isso ela vem quase sempre junto de COALESCE.

Concatenar, cortar e medir

SELECT CONCAT(<campo>, <campo>), CONCAT_WS('<separador>', <campo>, <campo>) FROM <tabela>;
SELECT UPPER(<campo>), LOWER(<campo>), LENGTH(<campo>), TRIM(<campo>) FROM <tabela>;
SELECT SUBSTRING(<campo>, <inicio>, <tamanho>), LEFT(<campo>, <n>), RIGHT(<campo>, <n>) FROM <tabela>;
SELECT REPLACE(<campo>, <procurado>, <troca>) FROM <tabela>;

SUBSTRING conta de 1, como no manual. TRIM sem argumento apaga os espaços das

duas pontas; TRIM(BOTH 'x' FROM campo) apaga o caractere indicado. LENGTH conta

bytes em utf8mb4, então texto com acento devolve número maior que a contagem de

letras.

Funções de data

Função de data devolve data ou número, dependendo do que se faça com ela.

NOW() e CURDATE() leem o relógio do servidor, e não o do PHP.

Extrair, formatar e comparar datas

SELECT YEAR(<data>), MONTH(<data>), DAY(<data>) FROM <tabela>;
SELECT DATE_FORMAT(<data>, '<formato>') FROM <tabela>;
SELECT DATEDIFF(<data1>, <data2>), DATE_ADD(<data>, INTERVAL <n> <unidade>) FROM <tabela>;
SELECT CURDATE(), NOW(), LAST_DAY(CURDATE());

DATEDIFF(a, b) devolve a diferença em dias, de a para b: o resultado

fica negativo se a primeira data for anterior.

Funções matemáticas e de substituição

ROUND, CEIL e FLOOR ajustam o decimal, e ROUND(10.567, 2) arredonda em

duas casas — o segundo argumento é o número de casas, não o de dígitos.

Ajustar número e tratar vazio

SELECT ROUND(<n>, <casas>), CEIL(<n>), FLOOR(<n>), ABS(<n>) FROM <tabela>;
SELECT COALESCE(<coluna1>, <coluna2>, 'padrao') FROM <tabela>;
SELECT IFNULL(<coluna>, 'padrao') FROM <tabela>;
SELECT CASE WHEN <condicao> THEN <resultado> ELSE <resultado> END FROM <tabela>;

COALESCE aceita quantas colunas quiser e para na primeira que não é NULL.

IFNULL aceita duas, e é o mesmo comportamento com menos opção. CASE é o

if do SQL: procura os WHEN na ordem, devolve o primeiro que fecha, e o ELSE

cobre o resto — sem ELSE, o resultado é NULL.

Exemplo

<?php
declare(strict_types=1);

// Funcoes de string, de data e matematicas: o que entra, o que sai.
// Tudo aqui roda dentro do SELECT, sem alterar dado nenhum.

$sql = <<<'SQL'
-- CONCAT junta colunas; com NULL em qualquer parte o resultado inteiro e NULL
SELECT CONCAT(nome, ' - ', cidade) AS identificacao, COALESCE(cidade, 'nao informada') AS local
FROM alunos
LIMIT 5;

-- CONCAT_WS usa um separador e pula os NULL em vez de anular o resultado
SELECT CONCAT_WS(' / ', nome, cidade, email) AS ficha FROM alunos LIMIT 5;

-- caixa, tamanho e corte
SELECT UPPER(nome) AS maiusculo, LOWER(email) AS minusculo, LENGTH(nome) AS caracteres
FROM cursos;

SELECT TRIM(nome) AS sem_espaco, LEFT(nome, 3) AS inicio, RIGHT(nome, 3) AS fim
FROM cursos;

-- SUBSTRING conta a partir de 1
SELECT SUBSTRING(nome, 1, 3) AS tres_primeiros, REPLACE(nome, ' ', '') AS junto
FROM cursos;

-- datas: extrair, formatar no padrao brasileiro, calcular diferenca em dias
SELECT YEAR(data_matricula) AS ano, MONTH(data_matricula) AS mes, DAY(data_matricula) AS dia
FROM matriculas
ORDER BY data_matricula DESC;

SELECT DATE_FORMAT(data_matricula, '%d/%m/%Y') AS data_br, DATEDIFF(NOW(), data_matricula) AS dias
FROM matriculas
ORDER BY data_matricula DESC;

SELECT CURDATE() AS hoje, NOW() AS agora, DATE_ADD(CURDATE(), INTERVAL 30 DAY) AS vence_em
FROM cursos;

-- matematica: o segundo argumento de ROUND e o numero de casas
SELECT ROUND(10.567, 2) AS arredondado, CEIL(10.1) AS topo, FLOOR(10.9) AS base, ABS(-7) AS modulo;

-- tratar valor ausente e classificar com CASE
SELECT IFNULL(telefone, 'nao informado') AS contato,
       CASE WHEN valor > 1000 THEN 'premium' ELSE 'padrao' END AS faixa
FROM cursos;
SQL;

echo $sql . "\n";

Saída real

-- CONCAT junta colunas; com NULL em qualquer parte o resultado inteiro e NULL
SELECT CONCAT(nome, ' - ', cidade) AS identificacao, COALESCE(cidade, 'nao informada') AS local
FROM alunos
LIMIT 5;

-- CONCAT_WS usa um separador e pula os NULL em vez de anular o resultado
SELECT CONCAT_WS(' / ', nome, cidade, email) AS ficha FROM alunos LIMIT 5;

-- caixa, tamanho e corte
SELECT UPPER(nome) AS maiusculo, LOWER(email) AS minusculo, LENGTH(nome) AS caracteres
FROM cursos;

SELECT TRIM(nome) AS sem_espaco, LEFT(nome, 3) AS inicio, RIGHT(nome, 3) AS fim
FROM cursos;

-- SUBSTRING conta a partir de 1
SELECT SUBSTRING(nome, 1, 3) AS tres_primeiros, REPLACE(nome, ' ', '') AS junto
FROM cursos;

-- datas: extrair, formatar no padrao brasileiro, calcular diferenca em dias
SELECT YEAR(data_matricula) AS ano, MONTH(data_matricula) AS mes, DAY(data_matricula) AS dia
FROM matriculas
ORDER BY data_matricula DESC;

SELECT DATE_FORMAT(data_matricula, '%d/%m/%Y') AS data_br, DATEDIFF(NOW(), data_matricula) AS dias
FROM matriculas
ORDER BY data_matricula DESC;

SELECT CURDATE() AS hoje, NOW() AS agora, DATE_ADD(CURDATE(), INTERVAL 30 DAY) AS vence_em
FROM cursos;

-- matematica: o segundo argumento de ROUND e o numero de casas
SELECT ROUND(10.567, 2) AS arredondado, CEIL(10.1) AS topo, FLOOR(10.9) AS base, ABS(-7) AS modulo;

-- tratar valor ausente e classificar com CASE
SELECT IFNULL(telefone, 'nao informado') AS contato,
       CASE WHEN valor > 1000 THEN 'premium' ELSE 'padrao' END AS faixa
FROM cursos;