SQL¶
SQL (Structured Query Language) é a linguagem padrão para conversar com um banco de dados relacional — criar/alterar tabelas, inserir, consultar, atualizar e remover dados. Os exemplos aqui usam MySQL, mas a sintaxe central é a mesma (com pequenas variações) em Oracle, PostgreSQL, SQL Server, ...
Definição: Convenção de maiúsculas em SQL
Não é uma regra do banco, mas uma convenção amplamente adotada: palavras-chave da
linguagem em maiúsculas (SELECT, CREATE TABLE, WHERE, ...) e nomes que você
escolheu (banco, tabela, coluna) em minúsculas. Isso deixa claro, ao ler um
comando, o que é sintaxe da linguagem e o que é específico do seu modelo.
Criando e selecionando um banco¶
CREATE DATABASE cria um banco novo (é comum ter um banco por projeto/aplicação);
USE diz ao servidor qual banco usar nos comandos seguintes.
Criando uma tabela¶
CREATE TABLE compras (
id INT AUTO_INCREMENT PRIMARY KEY,
valor DECIMAL(18,2),
data DATE,
observacoes VARCHAR(255),
recebida TINYINT
);
Toda tabela precisa de pelo menos uma coluna, e cada coluna declara um tipo:
| Tipo | Guarda |
|---|---|
INT |
número inteiro |
DECIMAL(precisao, escala) |
número decimal exato — precisao é o total de dígitos, escala é quantos ficam depois da vírgula |
DATE |
uma data (yyyy-MM-dd) |
VARCHAR(tamanho) |
texto de tamanho variável, até o máximo informado |
TINYINT |
inteiro pequeno — usado como substituto de boolean (0/1), já que bancos relacionais tradicionalmente não têm um tipo booleano nativo |
PRIMARY KEY marca a coluna como chave primária (ver
Modelagem de Dados); AUTO_INCREMENT faz o banco
gerar o próximo número sozinho a cada INSERT — você nunca informa o id manualmente.
Definição: Identificadores sem acento
Nomes de banco, tabela e coluna devem evitar acentos e caracteres especiais
(observacoes, não observações) — problemas de encoding entre sistemas
operacionais/ferramentas diferentes são uma fonte comum (e evitável) de bugs.
Para ver a estrutura de uma tabela já criada:
E para apagar uma tabela inteira (dados e estrutura, sem confirmação):
Para adicionar uma coluna a uma tabela que já existe (em vez de recriar do zero):
Inserindo dados: INSERT INTO¶
INSERT INTO compras (valor, data, observacoes, recebida)
VALUES (20, '2016-01-05', 'Lanchonete', 1);
Dois detalhes que geram erro com frequência para quem está começando:
- Formato de data: o padrão SQL é
yyyy-MM-dd(ano-mês-dia) — não o formato brasileirodd/MM/yyyy. É esse padrão (ISO 8601) que funciona de forma inequívoca entre bancos e localidades diferentes. - Separador decimal: ponto (
915.50), não vírgula —915,50seria interpretado como dois valores separados.
A lista de colunas entre parênteses ((valor, data, observacoes, recebida)) é
opcional, mas sem ela o INSERT assume a ordem exata em que as colunas foram
declaradas na tabela — omiti-la é arriscado se você não tiver certeza dessa ordem, e
obrigatório se quiser inserir os valores numa ordem diferente da declarada.
Consultando dados: SELECT¶
O * seleciona todas as colunas; FROM indica de qual tabela. O resultado de um
SELECT é sempre uma nova tabela (temporária, só para leitura) com as linhas que
combinam com a consulta.
Filtrando resultados: WHERE¶
WHERE filtra quais linhas entram no resultado, usando os operadores de comparação
usuais: = (igual), <, >, <=, >=. Texto é comparado entre aspas simples
(observacoes = 'Lanchonete').
Para combinar mais de uma condição, AND (as duas precisam ser verdadeiras) e OR
(pelo menos uma precisa ser verdadeira):
SELECT * FROM compras WHERE valor > 1500 AND recebida = 0;
SELECT * FROM compras WHERE valor < 500 OR valor > 1500;
Definição: Cuidado com AND em condições que se excluem
Um erro comum de quem está começando: tentar WHERE valor < 500 AND valor > 1500
esperando "valores fora da faixa 500-1500". Isso nunca retorna nada — nenhum
valor pode ser, ao mesmo tempo, menor que 500 e maior que 1500. Quando as
condições descrevem alternativas (uma coisa ou outra), o operador certo é
OR, não AND. AND é para quando a mesma linha precisa satisfazer as duas
condições simultaneamente.
Para buscar um pedaço de um texto (em vez de uma igualdade exata), LIKE com o
caractere curinga % (substitui qualquer sequência de caracteres, inclusive vazia):
SELECT * FROM compras WHERE observacoes LIKE 'Parcela%'; -- começa com "Parcela"
SELECT * FROM compras WHERE observacoes LIKE '%de%'; -- contém "de" em qualquer posição
Para filtrar um intervalo de valores, BETWEEN a AND b é equivalente a
>= a AND <= b, mas mais legível — funciona tanto para números quanto para datas:
Para negar uma condição qualquer, existe o operador NOT:
Atualizando dados: UPDATE¶
UPDATE tabela SET coluna = valor WHERE condição — para atualizar mais de uma coluna
de uma vez, separe por vírgula dentro do SET (não use AND — AND é sintaxe de
WHERE, não de SET):
O valor atribuído pode usar o valor atual de qualquer coluna da mesma linha — útil para aplicar um cálculo a várias linhas de uma vez, em vez de calcular manualmente e atualizar uma por uma:
Removendo dados: DELETE¶
Definição: Cuidado — UPDATE/DELETE sem WHERE afeta a tabela inteira
A regra de ouro para essas duas instruções: escreva e confira o WHERE antes
de rodar o UPDATE/DELETE. Sem WHERE, um UPDATE atualiza todas as
linhas da tabela com o mesmo valor, e um DELETE apaga todas as linhas — sem
aviso, sem confirmação, e (na maioria dos SGBDs) sem undo fácil depois. Um jeito
seguro de trabalhar: escreva a condição do WHERE primeiro, teste-a com um
SELECT, e só então acople o UPDATE/DELETE na frente dela.
NULL: ausência de valor¶
Por padrão, uma coluna aceita NULL — a ausência de qualquer valor. Um erro comum é
confundir NULL com "texto vazio" ('') ou com 0: os três são coisas diferentes.
NULL significa não se sabe/não se aplica, não "vazio" nem "zero".
Como NULL não é um valor de verdade, = não funciona para compará-lo — é preciso
IS NULL (ou IS NOT NULL):
Constraints: regras de integridade¶
Constraints são restrições que o banco garante sozinho, sem depender de código da
aplicação lembrar de validar. A mais comum é impedir valores NULL numa coluna que
não faz sentido ficar vazia:
-- numa tabela nova:
CREATE TABLE compras (
valor DECIMAL(18,2) NOT NULL,
data DATE NOT NULL,
...
);
-- numa tabela que já existe:
ALTER TABLE compras MODIFY COLUMN observacoes VARCHAR(255) NOT NULL;
ALTER TABLE ... MODIFY COLUMN altera a definição de uma coluna já existente (sem
perder os dados já cadastrados) — preferível a apagar (DROP TABLE) e recriar a
tabela do zero, que descartaria todos os registros.
Valores padrão: DEFAULT¶
Quando a maioria dos registros deveria começar com o mesmo valor numa coluna (em vez
de deixá-la NULL até alguém preencher), DEFAULT define esse valor automaticamente
quando o INSERT não o informa:
ALTER TABLE compras MODIFY COLUMN recebida TINYINT(1) DEFAULT 0;
INSERT INTO compras (valor, data, observacoes) VALUES (150, '2016-01-04', 'Compra de teste');
-- "recebida" vira 0 automaticamente, sem precisar informar
Funções de agregação e GROUP BY¶
Funções de agregação calculam um resultado a partir de várias linhas: SUM
(soma), COUNT (quantidade), AVG (média), entre outras.
SELECT SUM(valor) FROM compras; -- soma de todos os valores
SELECT COUNT(*) FROM compras WHERE recebida = 1; -- quantas compras recebidas
Definição: Por que uma função de agregação colapsa o resultado numa linha só
SELECT recebida, SUM(valor) FROM compras; não devolve "a soma de cada grupo de
recebida" como se poderia esperar — devolve uma única linha, com a soma de
tudo e um valor de recebida arbitrário (qualquer um dos existentes). É assim
porque uma função de agregação, por definição, colapsa várias linhas em uma só —
para agrupar por uma coluna específica, é preciso dizer isso explicitamente com
GROUP BY:
AS renomeia a coluna do resultado (um alias) — sem ele, o nome da coluna calculada
seria o próprio texto da função (SUM(valor)), pouco legível.
Ao agrupar por mais de uma característica (ex.: mês e ano), todas as colunas do
SELECT que não são agregação precisam estar no GROUP BY:
SELECT MONTH(data) AS mes, YEAR(data) AS ano, recebida, SUM(valor) AS soma
FROM compras
GROUP BY recebida, mes, ano;
YEAR(coluna) e MONTH(coluna) extraem o ano/mês de uma coluna DATE.
Definição: MAX/MIN, e como funções de agregação tratam NULL
Além de SUM/COUNT/AVG, MAX e MIN retornam o maior e o menor valor de um
grupo. De modo geral, funções de agregação ignoram valores NULL — COUNT(coluna)
conta só os valores não nulos daquela coluna, diferente de COUNT(*) (ver nota mais
abaixo sobre JOIN + agregação). Ao agrupar, o banco pode usar um índice já existente
sobre a coluna agrupada (evitando reordenar os dados) ou, na ausência de um, ordenar
os dados primeiro para então agrupá-los.
Ordenando resultados: ORDER BY¶
SELECT MONTH(data) AS mes, YEAR(data) AS ano, recebida, SUM(valor) AS soma
FROM compras
GROUP BY recebida, mes, ano
ORDER BY ano, mes;
ORDER BY prioriza as colunas na ordem em que aparecem no comando — no exemplo
acima, ordena por ano primeiro, e só usa mes para desempatar linhas do mesmo ano.
Trocar a ordem dos argumentos muda o critério de prioridade da ordenação.
Relacionando tabelas: FOREIGN KEY e JOIN¶
Para o banco impor a regra de que uma chave estrangeira (ver
Modelagem de Dados) só aponte para um registro que
realmente existe, criamos uma constraint FOREIGN KEY:
ALTER TABLE compras
ADD CONSTRAINT fk_compradores FOREIGN KEY (id_compradores)
REFERENCES compradores (id);
Definição: Integridade referencial
Depois que essa constraint existe, o banco recusa qualquer INSERT/UPDATE
em compras que tente usar um id_compradores que não exista de fato na tabela
compradores — e, na direção contrária, normalmente também impede apagar um
comprador que ainda tenha compras associadas a ele. Se já existirem dados
inconsistentes na tabela antes de criar a constraint (um id_compradores "órfão",
sem comprador correspondente), a própria criação da FOREIGN KEY falha até que
esses dados sejam corrigidos.
Para efetivamente combinar os dados das duas tabelas relacionadas numa única
consulta, usamos JOIN ... ON:
ON diz como as tabelas se relacionam — qual coluna de uma corresponde a qual
coluna da outra.
Definição: Produto cartesiano — o erro de listar tabelas sem condição de junção
Uma forma antiga (e hoje desencorajada) de combinar tabelas é listá-las direto no
FROM, separadas por vírgula, sem indicar como se relacionam:
JOIN ... ON explícito é a forma correta e clara de dizer qual combinação faz
sentido.
Restringindo valores de uma coluna: ENUM¶
Além de NOT NULL e FOREIGN KEY, o MySQL oferece ENUM — restringe uma coluna a um
conjunto fixo de valores possíveis:
ALTER TABLE compras ADD COLUMN forma_pagto ENUM('BOLETO', 'CREDITO');
INSERT INTO compras (..., forma_pagto) VALUES (..., 'BOLETO'); -- ok
INSERT INTO compras (..., forma_pagto) VALUES (..., 'DINHEIRO'); -- valor fora do ENUM
Definição: ENUM não é padrão ANSI SQL
Diferente de FOREIGN KEY, NOT NULL ou JOIN (que fazem parte do padrão SQL,
suportado por qualquer banco relacional), ENUM é uma extensão específica do
MySQL — outros bancos resolvem essa mesma necessidade de forma diferente (ou nem
oferecem um equivalente direto). Vale conferir a documentação do SGBD que você usa
antes de depender de um recurso assim.
SQL mode: comportamento estrito do servidor¶
O MySQL tem configurações de modo que mudam como ele reage a valores inválidos.
Por padrão, um INSERT com um valor fora do ENUM pode ser aceito silenciosamente
(virando um valor vazio, com só um aviso) — para forçar o banco a recusar (erro,
não aviso), habilita-se o modo estrito:
SET SESSION sql_mode = 'STRICT_ALL_TABLES'; -- só para esta sessão/conexão
SET GLOBAL sql_mode = 'STRICT_ALL_TABLES'; -- para todas as conexões futuras
SET SESSION vale só enquanto durar a conexão atual; ao reconectar, o servidor volta
ao modo padrão configurado globalmente — por isso, para um comportamento consistente
sempre, o ajuste deve ser feito com SET GLOBAL (ou na configuração do servidor).
Apelidando tabelas: alias¶
Em consultas com JOIN, repetir o nome inteiro da tabela em toda coluna
(aluno.nome, aluno.id, ...) é cansativo. Um alias — um apelido curto,
declarado logo após o nome da tabela — resolve isso:
aluno a define a como apelido de aluno; a partir daí, a.coluna é equivalente a
aluno.coluna. Alias também evitam ambiguidade quando duas tabelas na mesma consulta
têm uma coluna de mesmo nome (ex.: id em ambas).
Combinando mais de duas tabelas¶
Um JOIN pode ser encadeado quantas vezes forem necessárias, cada um com seu próprio
ON:
SELECT a.nome, c.nome FROM aluno a
JOIN matricula m ON m.aluno_id = a.id
JOIN curso c ON m.curso_id = c.id;
Isso combina aluno com curso, através da tabela associativa matricula no meio —
o padrão típico para consultar uma relação many to many (ver
Modelagem de Dados).
Verificando existência de relação: EXISTS/NOT EXISTS¶
Uma pergunta comum é "quais registros têm (ou não têm) um relacionado" — por
exemplo, alunos sem nenhuma matrícula. EXISTS verifica se uma subquery (uma
consulta dentro de outra) devolve pelo menos uma linha:
SELECT a.nome FROM aluno a
WHERE EXISTS (SELECT m.id FROM matricula m WHERE m.aluno_id = a.id);
-- alunos que TÊM ao menos uma matrícula
Combinado com NOT, inverte a pergunta — "quais não têm":
SELECT a.nome FROM aluno a
WHERE NOT EXISTS (SELECT m.id FROM matricula m WHERE m.aluno_id = a.id);
-- alunos SEM nenhuma matrícula
Definição: Subquery
Uma consulta SELECT usada dentro de outra — como argumento de EXISTS, ou em
qualquer lugar onde um valor/lista de valores é esperado. A subquery roda para cada
linha candidata da consulta externa (nos exemplos acima, uma vez para cada aluno),
verificando a condição específica daquela linha.
O mesmo padrão de NOT EXISTS serve para qualquer relação — encontrar cursos sem
matrícula, exercícios sem resposta, ou qualquer "órfão" numa relação um-para-muitos ou
muitos-para-muitos.
Definição: Construa uma consulta complexa aos pedaços
Uma consulta que encadeia vários JOINs (ex.: curso → secao → exercicio →
resposta → nota) fica muito mais fácil de escrever — e de depurar quando algo dá
errado — um JOIN de cada vez: comece com SELECT coluna FROM tabela1, rode,
confira o resultado, adicione o próximo JOIN, rode de novo, e repita até chegar
na consulta final com GROUP BY/agregação. Tentar escrever tudo de uma vez, sem
testar os passos intermediários, dificulta achar onde exatamente a consulta saiu do
esperado.
COUNT(coluna) (em vez de COUNT(*)) conta só os valores não nulos daquela
coluna especificamente — útil, por exemplo, ao contar alunos através de um JOIN
(COUNT(a.id)), garantindo que a contagem seja sobre a coluna certa mesmo se o JOIN
introduzir linhas repetidas por outros motivos.
Filtrando agregações: HAVING¶
WHERE filtra linhas individuais, antes de qualquer agrupamento — por isso não
funciona para filtrar pelo resultado de uma função de agregação:
SELECT a.nome, AVG(n.nota) FROM nota n
JOIN resposta r ON r.id = n.resposta_id
JOIN aluno a ON a.id = r.aluno_id
WHERE AVG(n.nota) < 5
GROUP BY a.nome;
-- ERROR 1111 (HY000): Invalid use of group function
Para filtrar pelo resultado de uma agregação (ex.: "só alunos com média abaixo de
5"), existe uma cláusula própria, HAVING, que roda depois do GROUP BY:
SELECT a.nome, AVG(n.nota) FROM nota n
JOIN resposta r ON r.id = n.resposta_id
JOIN aluno a ON a.id = r.aluno_id
GROUP BY a.nome
HAVING AVG(n.nota) < 5;
Definição: WHERE x HAVING
WHERE filtra antes de agrupar (linha a linha, nenhuma função de agregação
permitida ali). HAVING filtra depois de agrupar (só faz sentido usá-lo
quando a condição envolve uma função de agregação, como AVG, SUM, COUNT).
Sintaticamente, a ordem das cláusulas é sempre WHERE → GROUP BY → HAVING —
tentar escrever HAVING antes do GROUP BY é erro de sintaxe.
Eliminando duplicatas: DISTINCT¶
DISTINCT devolve só os valores únicos de uma coluna (ou combinação de colunas),
eliminando repetições do resultado.
Múltiplos valores numa condição: IN¶
Encadear vários OR para comparar a mesma coluna com valores diferentes
(tipo = 'PAGA_PJ' OR tipo = 'PAGA_PF' OR ...) funciona, mas cresce rápido e fica
difícil de ler. IN resume isso numa lista:
SELECT c.nome, COUNT(m.id), m.tipo FROM matricula m
JOIN curso c ON m.curso_id = c.id
WHERE m.tipo IN ('PAGA_PJ', 'PAGA_PF', 'PAGA_CHEQUE', 'PAGA_BOLETO')
GROUP BY c.nome, m.tipo;
coluna IN (valor1, valor2, ...) é equivalente a coluna = valor1 OR coluna = valor2
OR ..., mas muito mais legível — e mais fácil de manter conforme a lista de valores
cresce. Funciona com qualquer tipo de coluna, não só texto:
SELECT a.nome, c.nome FROM aluno a
JOIN matricula m ON m.aluno_id = a.id
JOIN curso c ON m.curso_id = c.id
WHERE a.id IN (1, 3, 4); -- os alunos de id 1, 3 ou 4
Subquery como coluna calculada¶
Além de usar uma subquery dentro de WHERE (como em EXISTS, visto acima), dá para
usar uma subquery no lugar de uma coluna, no SELECT — útil quando o valor que
falta para o relatório vem de uma consulta totalmente diferente da consulta principal.
Exemplo: um relatório que mostra a média de cada aluno em cada curso, e a diferença entre essa média e a média geral de todos os cursos. A consulta principal (aluno + curso + média do aluno) já é conhecida:
SELECT a.nome, c.nome, AVG(n.nota) as media_aluno FROM nota n
JOIN resposta r ON n.resposta_id = r.id
JOIN exercicio e ON r.exercicio_id = e.id
JOIN secao s ON e.secao_id = s.id
JOIN curso c ON s.curso_id = c.id
JOIN aluno a ON r.aluno_id = a.id
GROUP BY a.nome, c.nome;
Mas a média geral (SELECT AVG(n.nota) FROM nota n) é uma consulta separada, sem
relação de JOIN com a principal. A saída: colocar essa consulta entre parênteses no
lugar de uma coluna, e usá-la numa operação aritmética:
SELECT a.nome, c.nome, AVG(n.nota) as media_aluno,
AVG(n.nota) - (SELECT AVG(n.nota) FROM nota n) as diferenca
FROM nota n
JOIN resposta r ON n.resposta_id = r.id
JOIN exercicio e ON r.exercicio_id = e.id
JOIN secao s ON e.secao_id = s.id
JOIN curso c ON s.curso_id = c.id
JOIN aluno a ON r.aluno_id = a.id
GROUP BY a.nome, c.nome;
Definição: Subquery correlacionada
Uma subquery usada no lugar de uma coluna do SELECT, cujo resultado é combinado
(aqui, subtraído) com uma coluna da consulta principal. Só funciona se a
subquery devolver exatamente uma linha — se devolvesse mais de uma, o banco não
saberia com qual delas fazer a conta, e a consulta falha. É chamada de
"correlacionada" quando o valor que ela busca depende de uma coluna da linha atual
da consulta externa (ex.: WHERE r.aluno_id = a.id, filtrando pelo a.id da linha
de fora) — ao contrário da média geral do exemplo acima, que é a mesma para toda
linha e por isso não referencia nada da consulta externa.
Esse mesmo padrão — uma subquery no SELECT filtrada pela linha da consulta externa
— também serve para "contar quantos relacionados cada registro tem", sem precisar de
JOIN + GROUP BY:
SELECT a.nome,
(SELECT COUNT(r.id) FROM resposta r WHERE r.aluno_id = a.id) AS quantidade_respostas,
(SELECT COUNT(m.id) FROM matricula m WHERE m.aluno_id = a.id) AS quantidade_matricula
FROM aluno a;
Repare no WHERE r.aluno_id = a.id dentro de cada subquery: sem esse filtro, a
subquery contaria todas as respostas (ou matrículas) do banco, e todo aluno
receberia o mesmo número — é o filtro que faz a subquery rodar "uma vez para cada
aluno", trazendo o valor específico daquela linha.
Entendendo o LEFT JOIN¶
Um JOIN comum (chamado tecnicamente de inner join) só devolve uma linha quando
existe correspondência dos dois lados da junção. Isso vira um problema quando o
objetivo é justamente encontrar quem não tem correspondência — por exemplo, um
relatório de participação que deveria incluir também os alunos sem nenhuma resposta:
SELECT a.nome, COUNT(r.id) AS respostas
FROM aluno a
JOIN resposta r ON r.aluno_id = a.id
GROUP BY a.nome;
Se existem 16 alunos no banco mas a consulta acima devolve só 4, o motivo é o JOIN:
um aluno sem nenhuma resposta correspondente simplesmente desaparece do resultado,
porque não há linha de resposta para casar com ele.
Definição: LEFT JOIN
Variação do JOIN que preserva todas as linhas da tabela à esquerda (a que vem
logo depois do FROM), mesmo quando não existe correspondência na tabela à
direita — nesse caso, as colunas da tabela direita vêm como NULL. É a ferramenta
certa sempre que a pergunta é "todos os X, incluindo os que não têm Y relacionado".
SELECT a.nome, COUNT(r.id) AS respostas
FROM aluno a
LEFT JOIN resposta r ON r.aluno_id = a.id
GROUP BY a.nome;
-- agora os 16 alunos aparecem; quem não tem resposta mostra 0 (COUNT ignora os NULLs)
RIGHT JOIN é o espelho do LEFT JOIN — preserva todas as linhas da tabela à
direita. Na prática, quase não é usado: basta inverter a ordem das tabelas e trocar
por LEFT JOIN, então a maioria dos times padroniza em usar sempre LEFT JOIN por
consistência.
Definição: JOIN (inner) x LEFT JOIN — quando usar qual
Use JOIN comum quando a pergunta só faz sentido para quem tem o relacionamento
(ex.: "nota de cada resposta" — resposta sem nota não interessa). Use LEFT JOIN
quando a ausência de relacionamento também é uma informação relevante para a
resposta (ex.: "quais alunos não estão participando" — isso só aparece se o aluno
sem resposta continuar na lista).
JOIN sem qualificador é, tecnicamente, sempre um INNER JOIN — os dois termos
significam a mesma coisa; alguns times escrevem INNER JOIN explicitamente só para
deixar claro (por contraste com LEFT/RIGHT) que aquele JOIN exige associação dos
dois lados.
JOIN ou subquery?¶
Muita consulta que usa subquery (como as de contagem por aluno, vistas acima) também
pode ser escrita com JOIN + GROUP BY, com o mesmo resultado final. Qual preferir?
Definição: prefira JOIN a subquery quando o resultado é equivalente
SGBDs otimizam melhor consultas com JOIN do que consultas equivalentes com
subquery — o desempenho tende a ser melhor com JOIN. Quando as duas abordagens
resolvem o mesmo problema, prefira JOIN.
Uma armadilha ao combinar múltiplos LEFT JOINs independentes na mesma consulta:
juntar aluno com resposta e, na mesma consulta, com matricula (duas relações
"um-para-muitos" que não têm relação entre si) multiplica as linhas — cada resposta
do aluno é combinada com cada matrícula dele, gerando respostas × matrículas
linhas por aluno, em vez de contar cada uma separadamente:
SELECT a.nome, COUNT(r.id) AS qtd_respostas, COUNT(m.id) AS qtd_matriculas
FROM aluno a
LEFT JOIN resposta r ON r.aluno_id = a.id
LEFT JOIN matricula m ON m.aluno_id = a.id
GROUP BY a.nome;
-- ERRADO: um aluno com 7 respostas e 2 matrículas aparece com 14 em ambas as colunas
A saída é usar COUNT(DISTINCT ...), contando só os ids únicos de cada lado antes de
multiplicarem entre si:
SELECT a.nome, COUNT(DISTINCT r.id) AS qtd_respostas, COUNT(DISTINCT m.id) AS qtd_matriculas
FROM aluno a
LEFT JOIN resposta r ON r.aluno_id = a.id
LEFT JOIN matricula m ON m.aluno_id = a.id
GROUP BY a.nome;
Definição: por que JOINs independentes multiplicam linhas
Quando uma consulta faz JOIN de uma tabela com duas outras tabelas diferentes,
sem relação entre elas (aqui, resposta e matricula só se relacionam via
aluno, não uma com a outra), o banco combina cada linha de um lado com cada
linha do outro para o mesmo aluno — o mesmo efeito de um produto cartesiano, só
que restrito a cada aluno. COUNT(DISTINCT coluna) resolve porque conta valores
únicos daquela coluna, ignorando quantas vezes ela se repetiu por causa da
multiplicação. Nesse cenário específico (contar quantidades de duas relações
independentes de uma vez), a versão com subqueries correlacionadas (ver acima)
fica mais simples de acertar de primeira do que o JOIN duplo com DISTINCT.
Paginação: LIMIT¶
Devolver todas as linhas de uma tabela de uma vez só não escala — imagine listar todos os alunos de uma escola com milhões de registros, ou todas as mensagens antigas de uma conversa. O padrão é devolver os dados aos poucos (paginação), como uma rede social carrega só as postagens mais recentes e busca mais conforme você rola a tela.
LIMIT restringe a consulta a um número máximo de linhas — aqui, os 5 primeiros alunos
em ordem alfabética. Para pegar a próxima página, é preciso pular os já vistos: a
forma completa é LIMIT deslocamento, quantidade:
SELECT a.nome FROM aluno a ORDER BY a.nome LIMIT 5, 5;
-- pula os 5 primeiros (linhas 0-4) e traz os 5 seguintes
Definição: LIMIT deslocamento, quantidade
LIMIT sozinho (LIMIT 5) equivale a LIMIT 0, 5 — começa da primeira linha
(linha 0) e traz até 5. Com dois números, o primeiro é quantas linhas pular
a partir do início, o segundo é quantas trazer depois disso — é assim que se
implementa paginação (página 1 = LIMIT 0, 10, página 2 = LIMIT 10, 10, página 3
= LIMIT 20, 10, ...). Usar LIMIT sem ORDER BY funciona, mas não garante
qual subconjunto de linhas volta a cada execução — por isso paginação de verdade
sempre combina os dois.
Explorando um banco desconhecido pelo dicionário de dados¶
Quando não existe um Modelo de Entidade-Relacionamento (MER, ver Diagramas e UML 2) à mão para consultar, um banco Oracle (e a maioria dos bancos relacionais, com views equivalentes) permite descobrir a estrutura de um schema desconhecido consultando seu próprio dicionário de dados — útil tanto para montar uma consulta a partir de um enunciado em texto quanto para entender uma base legada sem documentação.
Definição: Views do dicionário de dados (Oracle)
user_tables/all_tables— listam as tabelas existentes (do próprio usuário, ou de todos que ele tem acesso).user_tab_comments/all_tab_comments— mostram comentários/descrição cadastrados sobre cada tabela, quando existirem.desc nome_tabela(comando do SQL*Plus, não uma view) — lista as colunas de uma tabela, seus tipos e se aceitamnull.user_constraints/user_cons_columns— permitem descobrir os relacionamentos (chaves estrangeiras) entre tabelas sem precisar de um MER: cruzando essas duas views é possível identificar quais colunas de uma tabela referenciam outra.
SELECT cons.table_name || '.' || cons_col.column_name ||
' faz ligação com ' ||
cons_depend.table_name || '.' || cons_col_depend.column_name ||
' através da chave ' || cons.constraint_name AS dependencias
FROM user_constraints cons, user_cons_columns cons_col,
user_constraints cons_depend, user_cons_columns cons_col_depend
WHERE cons.constraint_name = cons_col.constraint_name
AND cons.table_name = cons_col.table_name
AND cons.table_name = 'EMPLOYEES'
AND cons.constraint_type = 'R' -- 'R' = Foreign Key
AND cons_depend.constraint_name = cons_col_depend.constraint_name
AND cons_depend.table_name = cons_col_depend.table_name
AND cons_depend.constraint_name = cons.r_constraint_name
ORDER BY 1;
Definição: Roteiro para montar um SELECT a partir de um enunciado
Metodologia prática, análoga à usada para blocos PL/SQL (ver Uma metodologia para começar a escrever um bloco PL/SQL):
- Identifique as tabelas envolvidas — pelo nome explícito no enunciado, ou
inferindo pelo assunto (ex.: "departamentos" sugere uma tabela de
departamentos). Na dúvida, consulte
user_tables/all_tables. - Identifique as colunas de cada tabela com
desc tabela, conferindo se os nomes batem com o que o enunciado pede. - Escreva o
select/fromcom as colunas e tabelas identificadas, sempre usando aliases para tabelas e colunas — evita ambiguidade entre colunas de mesmo nome em tabelas diferentes, e deixa o comando mais legível. - Faça as ligações entre as tabelas na cláusula
where(oujoin), localizando as chaves estrangeiras via MER ou, na ausência dele, consultandouser_constraints/user_cons_columns. - Acrescente as demais restrições pedidas pelo enunciado (filtros adicionais,
order by).
O mesmo raciocínio (identificar tabelas → colunas → ligações → restrições) vale
igualmente para montar um insert, update ou delete.
Views: salvando uma consulta com nome¶
Definição: View
Tabela virtual baseada em uma consulta. Não guarda os dados: executa a consulta sempre que é usada, então reflete os dados atuais das tabelas de origem.
CREATE VIEW clientes_ativos AS
SELECT id, nome, email
FROM clientes
WHERE status = 'ATIVO';
SELECT * FROM clientes_ativos;
- Vantagem: simplifica consultas longas e repetidas e reutiliza a lógica.
- Segurança: pode expor só as colunas permitidas (ver permissões).
- Atenção: a view usa os dados atuais das tabelas de origem — se a estrutura delas mudar, a view pode quebrar.
Índices: acelerando buscas¶
Definição: Índice
Estrutura auxiliar que permite localizar dados mais rápido, sem varrer a tabela inteira (como o índice remissivo de um livro).
| Quando usar | Colunas muito usadas em WHERE, JOIN e ORDER BY |
| Vantagem | Reduz o tempo de leitura em tabelas grandes |
| Custo | INSERT, UPDATE e DELETE ficam um pouco mais lentos (o índice também precisa ser atualizado) e ocupa espaço |
| Boa prática | Crie índices com base em consultas reais; remova os não usados ou duplicados |
Plano de execução (EXPLAIN)¶
O comando EXPLAIN mostra o caminho que o banco planejou para executar uma consulta:
tabelas, índices, filtros e operações usadas, com uma estimativa de custo. Serve para
analisar consultas lentas antes de criar índices.
Sinal de alerta: leituras completas de tabelas grandes (full scan) podem indicar uma consulta ou um índice ruins. Otimize com dados reais e teste as mudanças.
Transações e propriedades ACID¶
Definição: Transação
Conjunto de operações tratado como uma única unidade: ou todas são efetivadas ou nenhuma. Evita dados incompletos ou inconsistentes (ex.: transferência — debitar uma conta e creditar outra).
BEGIN; -- inicia (em alguns bancos: START TRANSACTION)
UPDATE contas SET saldo = saldo - 100 WHERE id = 1;
UPDATE contas SET saldo = saldo + 100 WHERE id = 2;
COMMIT; -- confirma tudo
-- ou ROLLBACK; -- desfaz tudo se ocorrer erro
| Propriedade ACID | Significado | Exemplo |
|---|---|---|
| Atomicidade | Tudo acontece ou nada acontece | Compra só termina se pagamento e pedido forem registrados |
| Consistência | Os dados respeitam as regras do banco | Chaves e restrições continuam válidas |
| Isolamento | Transações simultâneas não se atrapalham | Dois clientes comprando o último item |
| Durabilidade | Após o COMMIT, a mudança permanece salva |
Mesmo se o servidor reiniciar |
(Transações em Oracle/PL/SQL em PL/SQL; em sistemas distribuídos, o modelo alternativo de consistência eventual está em NoSQL.)
SQL injection e prepared statements¶
Definição: SQL injection
Falha em que texto digitado pelo usuário é concatenado diretamente ao comando SQL, permitindo que uma entrada maliciosa altere a consulta e exponha, altere ou exclua dados.
-- MAL: monta o SQL juntando o que o usuário digitou
"SELECT * FROM usuarios WHERE login = '" + login + "' AND senha = '" + senha + "'"
-- entrada maliciosa: login = ' OR '1'='1
Definição: Prepared statement (consulta parametrizada)
Consulta preparada com marcadores de posição (?) cujos valores são enviados
separadamente da estrutura do comando. A entrada nunca é interpretada como SQL.
- Prevenção: use prepared statements e parâmetros; nunca monte SQL juntando texto recebido do usuário.
- Validação: valide formato, tamanho e tipo das entradas.
- Defesa extra: a conta que a aplicação usa deve ter poucos privilégios (ver Administração e Operação de Banco).
- No Java, o equivalente é o
PreparedStatementdo JDBC; em JPA/Spring Data, os parâmetros nomeados (@Param) — ver Spring.
SQL no Oracle: dialeto, objetos e usuários¶
Os exemplos acima usam MySQL. O Oracle Database segue o padrão SQL, mas tem um dialeto próprio e um ecossistema de objetos e de segurança bastante rico, muito presente em grandes empresas, bancos e governo. A linguagem procedural do Oracle está em PL/SQL; desempenho de consultas, em Performance e Tuning de SQL, Plano de execução e Índices. Ferramentas de linha de comando: SQL*Plus (vem com o banco) e interfaces gráficas como o SQL Developer.
MySQL x Oracle: principais diferenças¶
| Assunto | MySQL (acima) | Oracle |
|---|---|---|
| Texto / números | VARCHAR, INT, DECIMAL |
VARCHAR2(n), NUMBER(p,s), CHAR; objetos grandes CLOB/BLOB |
| Data | DATE, DATETIME |
DATE já guarda data e hora; TIMESTAMP para frações de segundo |
| Limitar linhas | LIMIT 10 |
FETCH FIRST 10 ROWS ONLY (12c+) ou WHERE ROWNUM <= 10 (clássico) |
| Chave automática | AUTO_INCREMENT |
Sequence + trigger, ou coluna GENERATED ... AS IDENTITY (12c+) |
SELECT sem tabela |
SELECT 1 + 1; |
SELECT 1 + 1 FROM DUAL; (DUAL é uma tabela de uma linha) |
| Concatenar | CONCAT(a, b) |
a \|\| b |
| Diferença de conjuntos | EXCEPT |
MINUS |
| Valores permitidos | ENUM |
Não existe: use CHECK (col IN ('A','B')) ou tabela de domínio |
| String vazia | '' e NULL são diferentes |
'' é tratado como NULL |
| Banco x usuário | CREATE DATABASE |
O "espaço" de objetos é o schema do usuário (CREATE USER) |
| Substituir nulo | IFNULL |
NVL, COALESCE |
-- Oracle: 5 maiores salários, de forma moderna e clássica
SELECT nome, salario FROM empregados ORDER BY salario DESC FETCH FIRST 5 ROWS ONLY;
SELECT * FROM (SELECT nome, salario FROM empregados ORDER BY salario DESC) WHERE ROWNUM <= 5;
Como o Oracle executa um comando SQL¶
Todo comando passa por três etapas:
- PARSE (análise): valida a sintaxe e procura, na memória compartilhada (shared pool), um plano já preparado para exatamente o mesmo texto; se achar, é um soft parse (barato). Se não, verifica no dicionário de dados se tabelas, colunas e permissões existem e o otimizador baseado em custo escolhe o plano de execução: é o hard parse (caro).
- EXECUTE: lê os dados (da memória, ou do disco se não estiverem em memória; leitura lógica x física). Em atualizações, trava (lock) as linhas e aloca o espaço de undo/rollback para poder desfazer.
- FETCH: devolve as linhas ao cliente (só em consultas).
Por isso o uso de variáveis bind (:valor) em vez de concatenar valores no texto do comando
reduz hard parses e protege contra SQL injection (Prepared statements).
Schemas, sessões e dicionário de dados¶
- Ao conectar com um usuário, o banco abre uma sessão. Cada usuário é dono de um schema (conjunto de seus
objetos: tabelas, views, packages, triggers). Objetos de outros usuários se referenciam como
schema.objeto(e só funcionam se o dono concedeu acesso). - O dicionário de dados são tabelas internas mantidas pelo próprio Oracle, expostas por views com
prefixo
USER_(meus objetos),ALL_(a que tenho acesso) eDBA_(todos):USER_TABLES,USER_TAB_COLUMNS,USER_CONSTRAINTS,USER_INDEXES,USER_OBJECTS. Nunca altere essas tabelas diretamente; os comandos DDL as atualizam. Técnica geral em Explorando um banco desconhecido.
Transações no Oracle¶
Uma transação começa na primeira instrução DML e termina com COMMIT ou ROLLBACK (ou uma instrução DDL,
que confirma implicitamente o que veio antes; QUIT normal também confirma). SAVEPOINT marca pontos
intermediários para um ROLLBACK TO. SET TRANSACTION inicia explicitamente uma transação com regras
definidas (por exemplo, somente leitura). Propriedades gerais em ACID e
detalhes de uso em PL/SQL: transações.
Criando tabelas no Oracle: detalhes úteis¶
-- Copiar estrutura E dados (CTAS)
CREATE TABLE emp_backup AS SELECT * FROM emp;
-- Tabela temporária global: os dados somem no fim da transação ou da sessão
CREATE GLOBAL TEMPORARY TABLE emp_tmp (
empno NUMBER(4), ename VARCHAR2(30)
) ON COMMIT DELETE ROWS; -- ou ON COMMIT PRESERVE ROWS (dura a sessão)
ALTER TABLE emp ADD (email VARCHAR2(100));
ALTER TABLE emp MODIFY (ename VARCHAR2(60) NOT NULL);
ALTER TABLE emp READ ONLY; -- somente leitura (volta com READ WRITE)
TRUNCATE TABLE emp_tmp; -- esvazia rápido; DDL: sem rollback, não dispara triggers
- Um
DEFAULTsó vale quando a coluna não é informada; se oINSERTpassaNULLexplicitamente, oNULLprevalece. - Ao desabilitar uma chave primária referenciada por outras tabelas, é preciso desabilitar também as chaves
estrangeiras dependentes (ou usar
CASCADE).
Constraints e mensagens de erro¶
| Constraint | Garante | Erro comum quando violada |
|---|---|---|
NOT NULL |
Coluna sempre preenchida | ORA-01400 (cannot insert NULL) |
PRIMARY KEY |
Unicidade e não nulo; cria índice | ORA-00001 (unique constraint violated) |
UNIQUE |
Valores não repetidos (aceita nulo) | ORA-00001 |
FOREIGN KEY |
Só referencia chave existente | ORA-02291 (parent key not found), ORA-02292 (child record found, ao apagar o pai) |
CHECK |
Regra sobre a linha (sal + comm > 10000) |
ORA-02290 (check constraint violated) |
ON DELETE CASCADE apaga os filhos junto com o pai; ON DELETE SET NULL zera a chave nos filhos. Dê nomes às
constraints (CONSTRAINT emp_dept_fk ...): sem nome, o Oracle gera um (SYS_C00123) que dificulta ler os erros.
Veja-as em USER_CONSTRAINTS e USER_CONS_COLUMNS.
Views, índices, sinônimos e sequences¶
- View:
CREATE OR REPLACE VIEW ... AS SELECT ...;WITH READ ONLYimpede alterações por ela eWITH CHECK OPTIONimpede inserir/atualizar linhas que a própria view não enxergaria. Materialized view guarda o resultado (com regra de atualização); a view comum guarda só a consulta. Conceito: Views. - Índices: o Oracle usa um índice quando as colunas do
WHEREaparecem sem função em volta (WHERE UPPER(nome) = ...ignora o índice denome, a menos que exista um índice por função). Tipos: B-tree (padrão), único, composto e bitmap (para colunas de baixa cardinalidade, como sexo ou status, em tabelas de consulta; evite em tabelas com muita escrita concorrente). Não dá para criar dois índices sobre a mesma lista de colunas. - Sinônimo: apelido para um objeto (tabela, view, sequence), muito usado para apontar para objetos de outro
schema sem escrever o prefixo. Pode ser privado (só do usuário) ou público (
CREATE PUBLIC SYNONYM). O sinônimo não dá acesso; oGRANTdá. - Sequence: gerador de números únicos, independente de tabela.
CREATE SEQUENCE seq_pedido START WITH 1000 INCREMENT BY 1 NOCACHE NOCYCLE;
INSERT INTO pedidos (id, cliente) VALUES (seq_pedido.NEXTVAL, 'ACME');
SELECT seq_pedido.CURRVAL FROM DUAL; -- só depois de um NEXTVAL na sessão
-- Oracle 12c+: coluna identity (sem trigger)
CREATE TABLE produtos (id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, nome VARCHAR2(80));
Parâmetros: START WITH, INCREMENT BY, MINVALUE/MAXVALUE, CYCLE e CACHE (pré-aloca números na memória). Podem
ocorrer lacunas (um ROLLBACK ou a perda do cache não devolve números), então não use a sequence como
contador sem falhas.
Usuários, privilégios e roles¶
CREATE USER app_vendas IDENTIFIED BY "senha-forte"
DEFAULT TABLESPACE users QUOTA 100M ON users;
GRANT CREATE SESSION TO app_vendas; -- privilégio de SISTEMA: sem ele, ORA-01045 ao conectar
GRANT CREATE TABLE, CREATE VIEW TO app_vendas; -- mais privilégios de sistema
GRANT SELECT, UPDATE (preco) ON loja.produtos TO app_vendas; -- de OBJETO (UPDATE só na coluna preco)
GRANT SELECT ON loja.produtos TO analista WITH GRANT OPTION; -- pode repassar o acesso
REVOKE UPDATE ON loja.produtos FROM app_vendas; -- (revoga na tabela inteira, não por coluna)
DROP USER app_vendas CASCADE; -- CASCADE se o usuário for dono de objetos
| Tipo | Exemplos | Quem repassa |
|---|---|---|
| Privilégio de sistema | CREATE SESSION, CREATE TABLE, CREATE SYNONYM, CREATE SEQUENCE |
WITH ADMIN OPTION |
| Privilégio de objeto | SELECT, INSERT, UPDATE, DELETE, EXECUTE, INDEX, REFERENCES |
WITH GRANT OPTION |
Role é um agrupamento de privilégios (de sistema e de objeto) concedido como se fosse um só; não tem dono e pode ser ativada ou desativada por sessão. Boas práticas e limites:
CREATE ROLE controla_empregado;
GRANT SELECT, INSERT, UPDATE ON loja.emp TO controla_empregado;
GRANT controla_empregado TO app_vendas;
ALTER USER app_vendas DEFAULT ROLE ALL EXCEPT excluir_empregado;
- Privilégios recebidos por role não valem em stored procedures nem para criar views ou chaves
estrangeiras sobre objetos de outro schema: nesses casos é preciso um
GRANTdireto ao usuário. - Roles predefinidas como
CONNECT,RESOURCEeDBAexistem por compatibilidade e dão muito poder. Em produção prefira roles próprias com o mínimo de privilégios (Administração de banco). - O usuário que acabou de ser criado não consegue sequer conectar até receber
CREATE SESSION.
Comandos úteis do SQL*Plus¶
| Comando | Para que serve |
|---|---|
L[IST], A[PPEND], C[HANGE] /velho/novo/, DEL, I[NPUT] |
Editar o comando no buffer do SQL*Plus |
/ |
Executa o conteúdo do buffer sem listá-lo |
GET, SAVE, EDIT, START ou @arquivo |
Ler, gravar, editar e executar scripts (@@ busca no diretório do script em execução) |
SPOOL arquivo / SPOOL OFF |
Grava a saída em arquivo (.lst por padrão) |
SET LINESIZE, SET PAGESIZE, SET HEADING, SET ECHO |
Variáveis de ambiente que controlam a exibição |
DEFINE, ACCEPT, &variavel, &&variavel |
Variáveis de substituição (veja PL/SQL: variáveis bind e de substituição) |
login.sql |
Script lido ao iniciar o SQL*Plus, para configurar o ambiente automaticamente |