Dia 10 — SQL: inserir e consultar: PHP e MySQL
INSERT
Inserir uma linha
INSERT INTO grava uma linha. O nome da tabela vem depois de INTO e os valores
na mesma ordem das colunas listadas; coluna omitida recebe NULL ou o DEFAULT
que a tabela define. String de texto vai entre aspas simples — '' é vazio,
NULL é ausência de valor, e são coisas diferentes.
A forma mais segura: nomear as colunas
INSERT INTO <tabela> (<coluna>, <coluna>, ...) VALUES (<valor>, <valor>, ...);
Nomear as colunas protege do dia em que alguém acrescentar uma coluna nova à
tabela: a instrução continua funcionando, enquanto `INSERT INTO tabela VALUES
(...)` quebra quando a tabela cresce.
Inserir várias linhas de uma vez
Um INSERT aceita várias tuplas separadas por vírgula, e o banco continua o
AUTO_INCREMENT sem repetir número entre elas. A diferença aparece quando são
milhares de linhas: uma instrução só, em vez de uma por linha.
A mesma forma, com a lista de valores
INSERT INTO <tabela> (<colunas>) VALUES (<valores>), (<valores>), (<valores>);
Copiar de uma tabela para outra
INSERT ... SELECT lê de uma tabela e grava em outra sem passar pelos valores.
Nele, SELECT vem no lugar de VALUES, e as colunas do SELECT devem
corresponder às colunas do destino.
A forma da cópia
INSERT INTO <tabela_destino> (<colunas>) SELECT <colunas> FROM <tabela_origem> WHERE <condicao>;
Se uma coluna obrigatória ficar sem valor, o INSERT é recusado: o erro vem com o
número da linha que falhou, o que dá para corrigir e tentar de novo.
Exemplo
<?php declare(strict_types=1); // As tres formas de INSERT: uma linha, varias linhas e copia de outra tabela. // Nao ha conexao: o script imprime o SQL que o aluno executa no cliente. $sql = <<<'SQL' -- 1) uma linha, com as colunas nomeadas (a forma segura) INSERT INTO cursos (nome, carga_horaria, valor) VALUES ('Banco de dados', 80, 1450.00); -- 2) varias linhas de uma vez: o AUTO_INCREMENT nao se repete entre elas INSERT INTO cursos (nome, carga_horaria, valor) VALUES ('Banco de dados', 80, 1450.00), ('Programacao PHP', 60, 1200.00), ('Seguranca da informacao',40, 980.50), ('Redes e infraestrutura', 50, 1100.00); -- 3) o que o banco faz sozinho quando a coluna e omitida INSERT INTO cursos (nome, carga_horaria, valor) VALUES ('Banco de dados', 80, 1450.00), ('Programacao PHP', 60, 1200.00); -- 4) INSERT SELECT: copia linhas de uma tabela para outra INSERT INTO cursos_desativados (nome, carga_horaria, valor) SELECT nome, carga_horaria, valor FROM cursos WHERE ativo = 0; -- conferir o resultado SELECT id, nome, carga_horaria, valor FROM cursos ORDER BY id; SQL; echo $sql . "\n";
Saída real
-- 1) uma linha, com as colunas nomeadas (a forma segura)
INSERT INTO cursos (nome, carga_horaria, valor)
VALUES ('Banco de dados', 80, 1450.00);
-- 2) varias linhas de uma vez: o AUTO_INCREMENT nao se repete entre elas
INSERT INTO cursos (nome, carga_horaria, valor) VALUES
('Banco de dados', 80, 1450.00),
('Programacao PHP', 60, 1200.00),
('Seguranca da informacao',40, 980.50),
('Redes e infraestrutura', 50, 1100.00);
-- 3) o que o banco faz sozinho quando a coluna e omitida
INSERT INTO cursos (nome, carga_horaria, valor)
VALUES ('Banco de dados', 80, 1450.00), ('Programacao PHP', 60, 1200.00);
-- 4) INSERT SELECT: copia linhas de uma tabela para outra
INSERT INTO cursos_desativados (nome, carga_horaria, valor)
SELECT nome, carga_horaria, valor
FROM cursos
WHERE ativo = 0;
-- conferir o resultado
SELECT id, nome, carga_horaria, valor FROM cursos ORDER BY id;
SELECT básico e WHERE
A forma do SELECT
SELECT escolhe as colunas, FROM diz de onde elas vêm. A ordem de leitura da
frase é o contrário: o SQL se escreve de fora para dentro, começando pelo que se
quer ver. O asterisco traz todas as colunas — prático no começo, ruim em código
que vai para produção, porque uma coluna nova aparece sozinha na tela.
A consulta mínima e a ordem das cláusulas
SELECT <coluna>, <coluna> FROM <tabela>; SELECT * FROM <tabela>;
A ordem das cláusulas é fixa: SELECT, FROM, WHERE, GROUP BY, HAVING,
ORDER BY, LIMIT. O erro mais comum é tentar colocar o WHERE antes do
FROM, e a resposta é sempre a mesma mensagem do servidor.
Filtrar com WHERE
WHERE fica depois do FROM e vale para a linha, não para a coluna. Ele combina
condições com AND, OR e NOT, e compara com =, <>, >, <, >= e <=.
Texto com acento compara exatamente como foi gravado: WHERE cidade = 'Sao Paulo'
não acha São Paulo.
Os três operadores lógicos e a precedência
SELECT <colunas> FROM <tabela> WHERE <condicao> AND <condicao>; SELECT <colunas> FROM <tabela> WHERE (<condicao> OR <condicao>) AND <condicao>;
AND tem precedência sobre OR: WHERE a = 1 OR b = 2 AND c = 3 é lido como
a = 1 OR (b = 2 AND c = 3). Quando a intenção era o outro sentido, os
parênteses resolvem — e essa é a forma mais segura de escrever.
| Maneira de escrever | O que faz |
|---|---|
<> ou != | Diferente de |
BETWEEN a AND b | Dentro da faixa, com as bordas |
IN (a, b, c) | Igual a um dos valores da lista |
LIKE 'texto%' | Começa com o texto |
IS NULL | A coluna não tem valor |
Exemplo
<?php declare(strict_types=1); // SELECT com WHERE: a ordem das clausulas e fixa e a combinacao de // condicoes e o que mais gera consulta errada. $sql = <<<'SQL' -- a forma minima: colunas e tabela SELECT id, nome, email FROM alunos; -- asterisco traz tudo: pratico no teste, perigoso em codigo de producao SELECT * FROM alunos; -- igualdade e os comparadores SELECT nome, cidade FROM alunos WHERE cidade = 'Sao Paulo'; SELECT nome FROM alunos WHERE idade >= 18 AND situacao = 'regular'; -- AND tem precedencia sobre OR; os parenteses dizem qual era a intencao SELECT nome FROM alunos WHERE (cidade = 'Sao Paulo' OR cidade = 'Campinas') AND ativo = 1; -- NOT nega a condicao SELECT nome FROM alunos WHERE NOT ativo = 1; -- varias condicoes com OR viram um IN mais legivel SELECT nome FROM cursos WHERE nome = 'Banco de dados' OR nome = 'Programacao PHP' OR nome = 'Redes'; -- o operador IN substitui a cadeia de OR e o NOT IN nega a lista SELECT nome FROM cursos WHERE nome IN ('Banco de dados', 'Programacao PHP', 'Redes'); -- IS NULL e o jeito correto de procurar coluna sem valor SELECT id, nome FROM alunos WHERE cidade IS NULL; SQL; echo $sql . "\n";
Saída real
-- a forma minima: colunas e tabela
SELECT id, nome, email FROM alunos;
-- asterisco traz tudo: pratico no teste, perigoso em codigo de producao
SELECT * FROM alunos;
-- igualdade e os comparadores
SELECT nome, cidade FROM alunos WHERE cidade = 'Sao Paulo';
SELECT nome FROM alunos WHERE idade >= 18 AND situacao = 'regular';
-- AND tem precedencia sobre OR; os parenteses dizem qual era a intencao
SELECT nome FROM alunos
WHERE (cidade = 'Sao Paulo' OR cidade = 'Campinas') AND ativo = 1;
-- NOT nega a condicao
SELECT nome FROM alunos WHERE NOT ativo = 1;
-- varias condicoes com OR viram um IN mais legivel
SELECT nome FROM cursos
WHERE nome = 'Banco de dados' OR nome = 'Programacao PHP' OR nome = 'Redes';
-- o operador IN substitui a cadeia de OR e o NOT IN nega a lista
SELECT nome FROM cursos
WHERE nome IN ('Banco de dados', 'Programacao PHP', 'Redes');
-- IS NULL e o jeito correto de procurar coluna sem valor
SELECT id, nome FROM alunos WHERE cidade IS NULL;
LIKE, IN, BETWEEN e IS NULL
LIKE
LIKE compara string com um padrão. % representa qualquer sequência de
caracteres, inclusive vazia, e _ representa exatamente um caractere. O padrão
costuma vir com aspas simples, mesmo sem %: WHERE nome LIKE 'Ana' é
exatamente igual a WHERE nome = 'Ana'.
A forma do padrão
SELECT <colunas> FROM <tabela> WHERE <coluna> LIKE '<padrao>%';
| Padrão | O que acha |
|---|---|
'Mar%' | Começa com Mar |
'%Silva' | Termina com Silva |
'%ar%' | Tem ar no meio |
'Programacao_php' | Exatamente um caractere no lugar do _ |
LIKE não usa índice, exceto no começo do padrão com VARCHAR. Se a busca for
por email ou por nome exato, = é mais rápido que LIKE '%termo%'.
IN, BETWEEN e IS NULL
IN testa se o valor está em uma lista e vale mais que uma cadeia de OR.
BETWEEN testa faixa, com as duas bordas incluídas. IS NULL verifica ausência
de valor, e é o único jeito correto de fazer isso: = NULL nunca devolve linha,
porque NULL = NULL é desconhecido para o SQL.
As três formas de filtro de conjunto
SELECT <colunas> FROM <tabela> WHERE <coluna> IN (<valor>, <valor>); SELECT <colunas> FROM <tabela> WHERE <coluna> BETWEEN <inicio> AND <fim>; SELECT <colunas> FROM <tabela> WHERE <coluna> IS [NOT] NULL;
| Busca | Filtro |
|---|---|
| Um dos valores da lista | IN (...) e NOT IN (...) |
| Dentro de uma faixa | BETWEEN a AND b |
| Coluna sem valor | IS NULL |
| Coluna preenchida | IS NOT NULL |
| Padrão de texto | LIKE, com % e _ |
Exemplo
<?php declare(strict_types=1); // Os quatro filtros de conjunto: padrao de texto, lista, faixa e vazio. // Todos entram depois do FROM e antes do ORDER BY. $sql = <<<'SQL' -- LIKE: % e qualquer sequencia, _ e exatamente um caractere SELECT id, nome FROM alunos WHERE nome LIKE 'Mar%'; SELECT id, nome FROM alunos WHERE nome LIKE '%Silva'; SELECT id, nome FROM alunos WHERE nome LIKE '%ar%'; SELECT id, nome FROM cursos WHERE nome LIKE 'Programacao_php'; -- NOT LIKE nega o padrao SELECT id, nome FROM alunos WHERE nome NOT LIKE 'Mar%'; -- IN: igual a um dos valores da lista SELECT id, nome FROM cursos WHERE nome IN ('Banco de dados', 'Programacao PHP', 'Redes e infraestrutura'); -- NOT IN: exclui os valores da lista SELECT id, nome FROM cursos WHERE nome NOT IN ('Banco de dados', 'Programacao PHP'); -- BETWEEN: faixa com as duas bordas incluidas SELECT id, nome, valor FROM cursos WHERE valor BETWEEN 900.00 AND 1300.00; SELECT id, nome FROM alunos WHERE nascimento BETWEEN '2000-01-01' AND '2004-12-31'; -- IS NULL e IS NOT NULL: coluna sem valor e coluna preenchida SELECT id, nome FROM alunos WHERE cidade IS NULL; SELECT id, nome, cidade FROM alunos WHERE cidade IS NOT NULL; -- a ordem das clausulas nao muda: SELECT, FROM, WHERE SELECT id, nome, cidade FROM alunos WHERE cidade IN ('Sao Paulo', 'Campinas') AND nome LIKE 'A%' ORDER BY nome; SQL; echo $sql . "\n";
Saída real
-- LIKE: % e qualquer sequencia, _ e exatamente um caractere
SELECT id, nome FROM alunos WHERE nome LIKE 'Mar%';
SELECT id, nome FROM alunos WHERE nome LIKE '%Silva';
SELECT id, nome FROM alunos WHERE nome LIKE '%ar%';
SELECT id, nome FROM cursos WHERE nome LIKE 'Programacao_php';
-- NOT LIKE nega o padrao
SELECT id, nome FROM alunos WHERE nome NOT LIKE 'Mar%';
-- IN: igual a um dos valores da lista
SELECT id, nome FROM cursos
WHERE nome IN ('Banco de dados', 'Programacao PHP', 'Redes e infraestrutura');
-- NOT IN: exclui os valores da lista
SELECT id, nome FROM cursos
WHERE nome NOT IN ('Banco de dados', 'Programacao PHP');
-- BETWEEN: faixa com as duas bordas incluidas
SELECT id, nome, valor FROM cursos WHERE valor BETWEEN 900.00 AND 1300.00;
SELECT id, nome FROM alunos WHERE nascimento BETWEEN '2000-01-01' AND '2004-12-31';
-- IS NULL e IS NOT NULL: coluna sem valor e coluna preenchida
SELECT id, nome FROM alunos WHERE cidade IS NULL;
SELECT id, nome, cidade FROM alunos WHERE cidade IS NOT NULL;
-- a ordem das clausulas nao muda: SELECT, FROM, WHERE
SELECT id, nome, cidade
FROM alunos
WHERE cidade IN ('Sao Paulo', 'Campinas') AND nome LIKE 'A%'
ORDER BY nome;
ORDER BY, LIMIT e paginação
ORDER BY
ORDER BY vem depois do WHERE e ordena o resultado pela coluna indicada, sem
alterar os dados guardados. Sem ASC ou DESC a ordenação é crescente. Números
ordenam por valor, texto ordena por ordem de alfabeto e NULL fica no começo em
ASC.
Uma ou várias colunas
SELECT <colunas> FROM <tabela> ORDER BY <coluna> [ASC|DESC], <coluna> [ASC|DESC];
Cada coluna tem sua própria direção, e a ordenação segue a ordem em que foram
escritas: primeiro por cidade, e dentro de uma mesma cidade, por nome
decrescente.
LIMIT e OFFSET
LIMIT corta o resultado depois de ordenado — nunca antes. O LIMIT sem
ORDER BY devolve linhas em ordem arbitrária, e a página 1 e a página 2 podem
mostrar exatamente o mesmo conjunto.
O par que faz a paginação
SELECT <colunas> FROM <tabela> ORDER BY <coluna> LIMIT <quantidade> OFFSET <quantidade>;
| Cláusula | O que faz |
|---|---|
LIMIT 10 | Devolve no máximo 10 linhas |
LIMIT 10, 20 | Salta 10 e devolve 20 — igual a LIMIT 20 OFFSET 10 |
LIMIT 20 OFFSET 40 | Página 3, com 20 linhas por página |
ORDER BY | Obrigatório antes do LIMIT em paginação |
A partir da segunda página o OFFSET começa a custar: o banco lê e descarta as
linhas anteriores. Em tabela grande, o padrão passa a ser buscar por chave —
WHERE id > 60 ORDER BY id LIMIT 20 — que não pula nada.
Exemplo
<?php declare(strict_types=1); // ORDER BY, LIMIT e paginacao. A ordenacao vem antes do corte: sem ela, // LIMIT devolve linhas em ordem arbitraria e a pagina se repete. $sql = <<<'SQL' -- ordenacao crescente (padrao) e decrescente SELECT id, nome, valor FROM cursos ORDER BY valor ASC; SELECT id, nome, valor FROM cursos ORDER BY valor DESC; -- varias colunas, cada uma com sua direcao SELECT nome, cidade FROM alunos ORDER BY cidade ASC, nome DESC; -- texto ordena por alfabeto, numero por valor, NULL fica no comeco em ASC SELECT id, nome, cidade FROM alunos ORDER BY cidade; -- LIMIT corta o resultado SELECT id, nome FROM cursos ORDER BY nome LIMIT 2; -- as duas formas de pedir 20 linhas pulando as 40 primeiras (pagina 3) SELECT id, nome FROM alunos ORDER BY nome LIMIT 20 OFFSET 40; SELECT id, nome FROM alunos ORDER BY nome LIMIT 40, 20; -- a pagina anterior e a seguinte, com a mesma ordenacao SELECT id, nome FROM alunos ORDER BY nome LIMIT 20 OFFSET 0; SELECT id, nome FROM alunos ORDER BY nome LIMIT 20 OFFSET 20; -- contagem total, para o rodape saber quantas paginas existem SELECT COUNT(*) AS total FROM alunos; SQL; echo $sql . "\n";
Saída real
-- ordenacao crescente (padrao) e decrescente SELECT id, nome, valor FROM cursos ORDER BY valor ASC; SELECT id, nome, valor FROM cursos ORDER BY valor DESC; -- varias colunas, cada uma com sua direcao SELECT nome, cidade FROM alunos ORDER BY cidade ASC, nome DESC; -- texto ordena por alfabeto, numero por valor, NULL fica no comeco em ASC SELECT id, nome, cidade FROM alunos ORDER BY cidade; -- LIMIT corta o resultado SELECT id, nome FROM cursos ORDER BY nome LIMIT 2; -- as duas formas de pedir 20 linhas pulando as 40 primeiras (pagina 3) SELECT id, nome FROM alunos ORDER BY nome LIMIT 20 OFFSET 40; SELECT id, nome FROM alunos ORDER BY nome LIMIT 40, 20; -- a pagina anterior e a seguinte, com a mesma ordenacao SELECT id, nome FROM alunos ORDER BY nome LIMIT 20 OFFSET 0; SELECT id, nome FROM alunos ORDER BY nome LIMIT 20 OFFSET 20; -- contagem total, para o rodape saber quantas paginas existem SELECT COUNT(*) AS total FROM alunos;