Jhonatan Pinheiro
Carregando página...
Jhonatan Pinheiro
Carregando página...
Jhonatan Pinheiro
Carregando página...
SELECT, filtros, ordenação, texto, números, datas, agregações e criação de tabelas: 200 comandos explicados com exemplo pronto para copiar.
Esta é a base do SQL — os comandos que você vai usar todo dia, em qualquer banco relacional. Cada item traz uma explicação curta e um exemplo pronto para copiar e testar.
Os exemplos usam PostgreSQL como referência. Quando um banco faz diferente (MySQL, SQL Server, Oracle, Firebird), a variação está anotada no próprio exemplo.
Dica de estudo: não decore. Rode cada comando, mude um pedaço e veja o que acontece. É assim que fixa.
Toda consulta começa aqui: escolher a origem dos dados e o que trazer de volta.
Traz apenas as colunas que você pediu. É o comando mais usado do SQL.
SELECT nome, email
FROM clientes;Traz tudo. Ótimo para explorar, ruim em produção: carrega dados que você não usa.
SELECT * FROM clientes;Renomeia a coluna no resultado. Deixa relatórios e APIs muito mais legíveis.
SELECT nome AS cliente, criado_em AS cadastro
FROM clientes;Encurta o nome da tabela. Vira essencial quando você começa a usar JOIN.
SELECT cli.nome, cli.email
FROM clientes AS cli;Elimina linhas repetidas do resultado.
SELECT DISTINCT cidade FROM clientes;A combinação de todas as colunas listadas é que precisa ser única.
SELECT DISTINCT cidade, estado FROM clientes;Serve para testar expressões e funções sem precisar de tabela. (No Oracle, use FROM dual.)
SELECT 2 + 2 AS resultado;
-- Oracle
SELECT 2 + 2 AS resultado FROM dual;Você pode fazer contas direto no SELECT e dar um nome ao resultado.
SELECT nome, preco, preco * 0.9 AS preco_com_desconto
FROM produtos;Junta textos. O padrão é ||; MySQL e SQL Server usam CONCAT().
-- Padrão SQL / PostgreSQL / Oracle / Firebird
SELECT nome || ' <' || email || '>' AS contato FROM clientes;
-- MySQL / SQL Server
SELECT CONCAT(nome, ' <', email, '>') AS contato FROM clientes;Nome completo do objeto: schema.tabela. Evita ambiguidade quando há tabelas homônimas.
SELECT * FROM vendas.pedidos;Precisa de nome com espaço ou maiúscula? Use aspas duplas (colchetes no SQL Server, crase no MySQL).
SELECT "Nome Completo" FROM clientes; -- padrão
SELECT [Nome Completo] FROM clientes; -- SQL ServerTudo depois de -- é ignorado. Use para explicar o porquê da query.
-- Clientes ativos da região Sul
SELECT * FROM clientes WHERE ativo = true;Comenta várias linhas de uma vez com /* */. Útil para desativar trechos temporariamente.
/* Relatório mensal
Autor: equipe de dados */
SELECT * FROM pedidos;O banco não lê de cima para baixo: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. Por isso não dá para usar apelido do SELECT no WHERE.
-- Erro: 'total' ainda não existe no WHERE
-- SELECT preco * qtd AS total FROM itens WHERE total > 100;
-- Certo
SELECT preco * qtd AS total FROM itens WHERE preco * qtd > 100;Encerra o comando. Obrigatório quando você envia vários comandos de uma vez.
SELECT 1;
SELECT 2;Filtrar bem é o que separa uma query rápida de uma que varre a tabela inteira.
Traz só as linhas em que a condição é verdadeira.
SELECT * FROM clientes WHERE cidade = 'Campinas';<> é o padrão SQL; != funciona na maioria dos bancos.
SELECT * FROM pedidos WHERE status <> 'cancelado';Comparadores >, <, >= e <= funcionam com número, texto e data.
SELECT * FROM produtos WHERE preco >= 100;Só retorna a linha se todas as condições forem verdadeiras.
SELECT * FROM produtos
WHERE preco >= 100 AND categoria = 'games';Retorna a linha se pelo menos uma condição for verdadeira.
SELECT * FROM clientes
WHERE cidade = 'Campinas' OR cidade = 'Sumaré';Inverte a condição.
SELECT * FROM produtos WHERE NOT esgotado;AND é avaliado antes de OR. Sem parênteses o resultado costuma ser diferente do esperado.
-- Errado: traz games caros OU qualquer livro
SELECT * FROM produtos WHERE categoria = 'games' AND preco > 100 OR categoria = 'livros';
-- Certo
SELECT * FROM produtos WHERE (categoria = 'games' OR categoria = 'livros') AND preco > 100;Atalho para vários OR sobre a mesma coluna.
SELECT * FROM clientes WHERE estado IN ('SP', 'RJ', 'MG');Cuidado: se a lista tiver NULL, o resultado vem vazio. Prefira NOT EXISTS nesse caso.
SELECT * FROM pedidos WHERE status NOT IN ('cancelado', 'estornado');Inclui os dois extremos. Equivale a >= a AND <= b.
SELECT * FROM produtos WHERE preco BETWEEN 50 AND 150;Tudo que está fora do intervalo.
SELECT * FROM produtos WHERE preco NOT BETWEEN 50 AND 150;% casa com qualquer sequência de caracteres, inclusive vazia.
SELECT * FROM clientes WHERE email LIKE '%@gmail.com';_ casa com exatamente um caractere.
-- Placas no formato ABC-1234
SELECT * FROM veiculos WHERE placa LIKE '___-____';PostgreSQL tem ILIKE; nos outros, normalize com UPPER() dos dois lados.
-- PostgreSQL
SELECT * FROM clientes WHERE nome ILIKE '%silva%';
-- Portável
SELECT * FROM clientes WHERE UPPER(nome) LIKE UPPER('%silva%');Para buscar um % ou _ literal, defina um caractere de escape.
-- Busca descontos que contenham '50%'
SELECT * FROM promocoes WHERE texto LIKE '%50!%%' ESCAPE '!';NULL é ausência de valor. Nunca use = NULL: a comparação nunca é verdadeira.
SELECT * FROM clientes WHERE telefone IS NULL;Só as linhas que têm valor preenchido.
SELECT * FROM clientes WHERE telefone IS NOT NULL;Verdadeiro se a subconsulta retorna ao menos uma linha. Costuma ser mais rápido que IN com muitas linhas.
SELECT cli.nome FROM clientes cli
WHERE EXISTS (SELECT 1 FROM pedidos ped WHERE ped.cliente_id = cli.id);Compare com literais de data. Para intervalos, prefira >= início AND < fim (evita perder as horas do último dia).
SELECT * FROM pedidos
WHERE criado_em >= DATE '2026-01-01'
AND criado_em < DATE '2026-02-01';Em bancos com tipo booleano, a coluna já é a condição.
SELECT * FROM clientes WHERE ativo;
SELECT * FROM clientes WHERE NOT ativo;
-- MySQL / Oracle / Firebird (0 e 1)
SELECT * FROM clientes WHERE ativo = 1;Sem ORDER BY não existe ordem garantida — nem que pareça ordenado nos testes.
Ordena o resultado. Crescente (ASC) é o padrão.
SELECT nome FROM clientes ORDER BY nome;Ordem decrescente: mais recente, maior valor, etc.
SELECT * FROM pedidos ORDER BY criado_em DESC;Aplica na sequência: empate na primeira, desempata pela segunda.
SELECT * FROM clientes ORDER BY estado ASC, nome ASC;ORDER BY 2 ordena pela segunda coluna do SELECT. Prático em consultas rápidas, frágil em código.
SELECT cidade, COUNT(*) FROM clientes GROUP BY cidade ORDER BY 2 DESC;Controla onde os nulos aparecem (PostgreSQL, Oracle, Firebird).
SELECT * FROM clientes ORDER BY telefone NULLS LAST;Retorna no máximo N linhas (PostgreSQL, MySQL, SQLite).
SELECT * FROM produtos ORDER BY preco DESC LIMIT 10;Pula as N primeiras. Base da paginação simples.
SELECT * FROM produtos ORDER BY id LIMIT 10 OFFSET 20;Equivalente ao LIMIT no SQL Server.
SELECT TOP 10 * FROM produtos ORDER BY preco DESC;Sintaxe padrão, aceita por Oracle 12c+, PostgreSQL, SQL Server 2012+ e Firebird 3+.
SELECT * FROM produtos
ORDER BY preco DESC
OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY;Página N de tamanho T: pule (N-1)*T. Sempre com ORDER BY estável, senão itens se repetem entre páginas.
-- Página 3, 20 por página
SELECT * FROM produtos ORDER BY id LIMIT 20 OFFSET 40;As peças que você combina dentro de SELECT, WHERE e ORDER BY.
Soma, subtração, multiplicação e divisão funcionam como você espera.
SELECT preco + frete AS total, qtd * preco AS subtotal FROM itens;Dividir dois inteiros pode truncar o resultado. Converta antes se quiser casas decimais.
SELECT 7 / 2; -- 3 em PostgreSQL/Oracle
SELECT 7 / 2.0; -- 3.5
SELECT CAST(7 AS DECIMAL) / 2; -- 3.5% na maioria dos bancos, MOD() no padrão e no Oracle.
SELECT 10 % 3 AS resto; -- 1
SELECT MOD(10, 3) AS resto; -- 1Eleva um número a um expoente.
SELECT POWER(2, 10) AS resultado; -- 1024Multiplicação e divisão vêm antes de soma e subtração. Parênteses resolvem qualquer dúvida.
SELECT 2 + 3 * 4 AS sem_parenteses, -- 14
(2 + 3) * 4 AS com_parenteses; -- 20Conversão explícita e portável entre tipos.
SELECT CAST('2026-01-15' AS DATE) AS data,
CAST(preco AS INTEGER) AS preco_inteiro
FROM produtos;Atalho do PostgreSQL para CAST.
SELECT '42'::INTEGER, criado_em::DATE FROM pedidos;Compara uma expressão com valores fixos, como um switch.
SELECT nome,
CASE status
WHEN 'P' THEN 'Pendente'
WHEN 'A' THEN 'Aprovado'
ELSE 'Outro'
END AS situacao
FROM pedidos;Cada ramo tem sua própria condição — mais flexível que o CASE simples.
SELECT nome,
CASE
WHEN preco < 50 THEN 'barato'
WHEN preco < 200 THEN 'médio'
ELSE 'caro'
END AS faixa
FROM produtos;Retorna o primeiro argumento que não for NULL. Ideal para valores padrão.
SELECT nome, COALESCE(apelido, nome, 'sem nome') AS exibicao FROM clientes;Retorna NULL quando os dois valores são iguais. Clássico para evitar divisão por zero.
SELECT total / NULLIF(quantidade, 0) AS preco_medio FROM pedidos;Maior e menor entre vários valores na mesma linha (não confundir com MAX/MIN, que são entre linhas).
SELECT GREATEST(preco_site, preco_loja) AS maior,
LEAST(preco_site, preco_loja) AS menor
FROM produtos;A comparação segue o collation do banco — que define acentuação e maiúsculas.
SELECT * FROM clientes WHERE nome > 'M' ORDER BY nome;PostgreSQL tem BOOLEAN nativo; MySQL usa TINYINT(1); Oracle e Firebird antigos usam CHAR(1) ou SMALLINT.
-- PostgreSQL
SELECT * FROM clientes WHERE ativo = TRUE;
-- MySQL / Oracle / Firebird
SELECT * FROM clientes WHERE ativo = 1;Você pode ordenar por um cálculo, não só por colunas.
SELECT nome, preco, estoque
FROM produtos
ORDER BY preco * estoque DESC;Limpar, formatar e comparar strings é metade do trabalho com dados do mundo real.
Converte o texto todo para maiúsculas.
SELECT UPPER(nome) FROM clientes;Converte para minúsculas. Útil para normalizar e-mails antes de comparar.
SELECT LOWER(email) FROM clientes;Deixa a inicial de cada palavra em maiúscula (PostgreSQL e Oracle).
SELECT INITCAP('maria da silva'); -- Maria Da SilvaConta caracteres. No SQL Server chama-se LEN.
SELECT nome, LENGTH(nome) AS tamanho FROM clientes;
-- SQL Server
SELECT nome, LEN(nome) FROM clientes;Tira espaços das duas pontas. Essencial em dados importados.
SELECT TRIM(' texto ') AS limpo;Removem espaços só da esquerda ou só da direita.
SELECT LTRIM(' abc'), RTRIM('abc ');Dá para remover qualquer caractere, não só espaço.
SELECT TRIM(BOTH '0' FROM '000123000'); -- 123Extrai um pedaço a partir de uma posição (a contagem começa em 1).
SELECT SUBSTRING(cpf FROM 1 FOR 3) AS inicio FROM clientes;
-- MySQL / SQL Server
SELECT SUBSTRING(cpf, 1, 3) FROM clientes;Pega os N primeiros caracteres.
SELECT LEFT(nome, 1) AS inicial FROM clientes;Pega os N últimos caracteres.
SELECT RIGHT(telefone, 4) AS final FROM clientes;Troca todas as ocorrências de um trecho por outro.
SELECT REPLACE(telefone, '-', '') AS so_digitos FROM clientes;Devolve onde um trecho começa (0 se não achar). INSTR no Oracle/MySQL, CHARINDEX no SQL Server.
SELECT POSITION('@' IN email) AS pos FROM clientes;
-- SQL Server
SELECT CHARINDEX('@', email) FROM clientes;Completa o texto até um tamanho fixo. Clássico para códigos com zeros à esquerda.
SELECT LPAD(CAST(id AS VARCHAR), 6, '0') AS codigo FROM pedidos; -- 000042Mesma ideia, completando do lado direito.
SELECT RPAD(nome, 20, '.') AS coluna_fixa FROM clientes;Concatena ignorando os NULL, colocando um separador entre os valores.
SELECT CONCAT_WS(', ', cidade, estado, pais) AS endereco FROM clientes;Inverte a ordem dos caracteres.
SELECT REVERSE('abcdef'); -- fedcbaRepete um texto N vezes (REPLICATE no SQL Server).
SELECT REPEAT('-', 20) AS linha;Pega a N-ésima parte de um texto separado por um delimitador (PostgreSQL).
SELECT SPLIT_PART(email, '@', 2) AS dominio FROM clientes;Combinação clássica de SUBSTRING com POSITION, portável entre bancos.
SELECT SUBSTRING(email FROM POSITION('@' IN email) + 1) AS dominio
FROM clientes;Concatena valores de várias linhas em um texto só. GROUP_CONCAT no MySQL, LISTAGG no Oracle.
SELECT cliente_id, STRING_AGG(produto, ', ') AS itens
FROM pedidos GROUP BY cliente_id;Converte número ou data em texto com máscara (PostgreSQL, Oracle).
SELECT TO_CHAR(total, 'FM999G999D00') AS valor FROM pedidos;Converte caractere em código e vice-versa (CHAR no SQL Server/MySQL).
SELECT ASCII('A') AS codigo, CHR(65) AS letra;Substitui vários caracteres de uma vez, um a um. Ótimo para remover pontuação.
SELECT TRANSLATE(cpf, '.-', '') AS so_digitos FROM clientes;Filtros que o LIKE não dá conta. ~ no PostgreSQL, REGEXP no MySQL/Oracle.
-- PostgreSQL: só dígitos
SELECT * FROM clientes WHERE telefone ~ '^[0-9]+$';
-- MySQL
SELECT * FROM clientes WHERE telefone REGEXP '^[0-9]+$';Truque portável: compara o tamanho antes e depois de remover o caractere.
SELECT LENGTH(tags) - LENGTH(REPLACE(tags, ',', '')) + 1 AS qtd_tags
FROM produtos;Arredondamento e precisão parecem detalhe até virarem diferença no relatório financeiro.
Arredonda para o número de casas indicado.
SELECT ROUND(1234.5678, 2) AS valor; -- 1234.57Menor inteiro maior ou igual ao valor (CEILING no SQL Server).
SELECT CEIL(4.1) AS resultado; -- 5Maior inteiro menor ou igual ao valor.
SELECT FLOOR(4.9) AS resultado; -- 4Corta as casas decimais sem arredondar.
SELECT TRUNC(4.99, 1) AS resultado; -- 4.9Remove o sinal do número.
SELECT ABS(-42) AS resultado; -- 42Retorna -1, 0 ou 1 conforme o sinal do número.
SELECT SIGN(saldo) AS situacao FROM contas;Raiz quadrada do valor.
SELECT SQRT(144) AS resultado; -- 12Exponencial natural e logaritmo natural — base de cálculos de crescimento.
SELECT EXP(1) AS euler, LN(EXP(1)) AS um;Logaritmo em uma base qualquer.
SELECT LOG(10, 1000) AS resultado; -- 3Gera valor entre 0 e 1 (RAND() no MySQL/SQL Server). Útil para amostragem.
SELECT * FROM clientes ORDER BY RANDOM() LIMIT 10;Multiplique por 100 e arredonde. Cuidado com divisão por zero.
SELECT ROUND(100.0 * aprovados / NULLIF(total, 0), 1) AS pct FROM metricas;Cálculo direto no SELECT, sem precisar de aplicação.
SELECT preco, ROUND(preco * (1 - desconto / 100.0), 2) AS final FROM produtos;Use DECIMAL/NUMERIC para dinheiro. Com FLOAT você acumula erro de ponto flutuante.
SELECT CAST(total AS DECIMAL(12,2)) AS valor FROM pedidos;DECIMAL é exato (dinheiro); FLOAT é aproximado (medições, ciência).
SELECT 0.1 + 0.2 = 0.3 AS float_confia; -- pode dar falso
SELECT CAST(0.1 AS DECIMAL(2,1)) + CAST(0.2 AS DECIMAL(2,1));Converta antes de comparar ou somar; texto ordena '10' antes de '9'.
SELECT CAST('42' AS INTEGER) + 8 AS resultado;Conversão explícita evita erro de concatenação.
SELECT 'Pedido #' || CAST(id AS VARCHAR) AS titulo FROM pedidos;Multiplique por 1.0 (ou converta) para não cair na divisão inteira.
SELECT SUM(total) * 1.0 / COUNT(*) AS ticket_medio FROM pedidos;Distribui valores em faixas de tamanho igual — histograma direto no SQL (PostgreSQL, Oracle).
SELECT WIDTH_BUCKET(preco, 0, 1000, 10) AS faixa, COUNT(*)
FROM produtos GROUP BY 1 ORDER BY 1;generate_series (PostgreSQL) cria linhas sob demanda — ótimo para calendários e testes.
SELECT * FROM generate_series(1, 10) AS num;Entradas e saídas na mesma coluna, somadas conforme o tipo.
SELECT SUM(CASE WHEN tipo = 'saida' THEN -valor ELSE valor END) AS saldo
FROM movimentos;Data é o tipo que mais gera bug: fuso, fim de mês e comparação de intervalo.
Data do servidor, sem hora.
SELECT CURRENT_DATE;
-- SQL Server
SELECT CAST(GETDATE() AS DATE);Data e hora do servidor. NOW() no PostgreSQL/MySQL, SYSDATE no Oracle.
SELECT CURRENT_TIMESTAMP;
SELECT NOW(); -- PostgreSQL / MySQL
SELECT SYSDATE FROM dual; -- OracleUse o formato ISO AAAA-MM-DD — é o único sem ambiguidade entre bancos e locales.
SELECT * FROM pedidos WHERE criado_em >= DATE '2026-01-01';Isola ano, mês, dia, hora, minuto ou segundo.
SELECT EXTRACT(YEAR FROM criado_em) AS ano,
EXTRACT(MONTH FROM criado_em) AS mes
FROM pedidos;Mesma ideia do EXTRACT, com sintaxe de função (PostgreSQL).
SELECT DATE_PART('dow', criado_em) AS dia_semana FROM pedidos;Versão do SQL Server para extrair partes da data.
SELECT DATEPART(YEAR, criado_em) AS ano FROM pedidos;Atalhos diretos no MySQL e no SQL Server.
SELECT YEAR(criado_em), MONTH(criado_em), DAY(criado_em) FROM pedidos;Zera as partes menores: agrupa por dia, mês ou ano sem perder o tipo data.
SELECT DATE_TRUNC('month', criado_em) AS mes, COUNT(*)
FROM pedidos GROUP BY 1 ORDER BY 1;Com INTERVAL no padrão; cada banco tem seu atalho.
SELECT CURRENT_DATE + INTERVAL '7 days'; -- PostgreSQL
SELECT DATE_ADD(CURDATE(), INTERVAL 7 DAY); -- MySQL
SELECT DATEADD(DAY, 7, GETDATE()); -- SQL ServerMesma lógica com sinal invertido.
SELECT CURRENT_DATE - INTERVAL '30 days' AS trinta_dias_atras;Cuidado com fim de mês: 31/01 + 1 mês costuma virar 28/02.
SELECT CURRENT_DATE + INTERVAL '1 month';
SELECT ADD_MONTHS(SYSDATE, 1) FROM dual; -- OracleNo PostgreSQL a subtração devolve intervalo; nos outros existe função dedicada.
SELECT entrega - pedido AS dias FROM pedidos; -- PostgreSQL
SELECT DATEDIFF(entrega, pedido) FROM pedidos; -- MySQL
SELECT DATEDIFF(DAY, pedido, entrega) FROM pedidos; -- SQL ServerDevolve a diferença em anos/meses/dias (PostgreSQL).
SELECT AGE(CURRENT_DATE, nascimento) AS idade FROM clientes;Truque portável: diferença de anos, ajustada se o aniversário ainda não passou.
SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, nascimento)) AS idade
FROM clientes;DATE_TRUNC resolve sem gambiarra de string.
SELECT DATE_TRUNC('month', CURRENT_DATE) AS primeiro_dia;Primeiro dia do mês seguinte menos um dia. LAST_DAY() no MySQL/Oracle.
SELECT DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month' - INTERVAL '1 day';
SELECT LAST_DAY(CURRENT_DATE); -- MySQL / OracleRetorna o número do dia (a base varia: 0 ou 1, domingo ou segunda — confira no seu banco).
SELECT EXTRACT(DOW FROM criado_em) AS dia FROM pedidos; -- 0 = domingo
SELECT DAYOFWEEK(criado_em) FROM pedidos; -- MySQL: 1 = domingoFormatação por máscara.
SELECT TO_CHAR(criado_em, 'Month') AS mes,
TO_CHAR(criado_em, 'Day') AS dia
FROM pedidos;Cada banco tem sua função de formatação.
SELECT TO_CHAR(criado_em, 'DD/MM/YYYY'); -- PostgreSQL / Oracle
SELECT DATE_FORMAT(criado_em, '%d/%m/%Y'); -- MySQL
SELECT FORMAT(criado_em, 'dd/MM/yyyy'); -- SQL ServerInforme a máscara de entrada para não depender do locale do servidor.
SELECT TO_DATE('15/01/2026', 'DD/MM/YYYY'); -- PostgreSQL / Oracle
SELECT STR_TO_DATE('15/01/2026', '%d/%m/%Y'); -- MySQLConverte um timestamp para outro fuso. Guarde sempre em UTC e converta na exibição.
SELECT criado_em AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo'
FROM pedidos;Converte entre epoch (segundos desde 1970) e data.
SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) AS epoch;
SELECT TO_TIMESTAMP(1767225600) AS data;Prefira intervalo com >= e <: assim o índice da coluna continua sendo usado.
SELECT * FROM pedidos
WHERE criado_em >= DATE_TRUNC('month', CURRENT_DATE)
AND criado_em < DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month';Janela móvel, muito usada em dashboards.
SELECT * FROM pedidos WHERE criado_em >= CURRENT_DATE - INTERVAL '30 days';WHERE YEAR(coluna) = 2026 impede o uso do índice. Reescreva como intervalo.
-- Lento
SELECT * FROM pedidos WHERE EXTRACT(YEAR FROM criado_em) = 2026;
-- Rápido (usa índice)
SELECT * FROM pedidos
WHERE criado_em >= DATE '2026-01-01' AND criado_em < DATE '2027-01-01';NULL não é zero nem string vazia: é 'desconhecido'. Isso muda toda a lógica.
Ausência de valor. Qualquer comparação com NULL resulta em 'desconhecido', não em verdadeiro ou falso.
SELECT NULL = NULL AS comparacao; -- NULL, não TRUEQualquer conta com NULL resulta em NULL. Trate antes com COALESCE.
SELECT 10 + NULL AS resultado; -- NULL
SELECT 10 + COALESCE(NULL, 0) AS tratado; -- 10No padrão SQL, concatenar com NULL anula tudo. CONCAT() do MySQL ignora.
SELECT 'Olá ' || NULL AS resultado; -- NULL
SELECT 'Olá ' || COALESCE(nome, 'visitante') FROM clientes;COUNT(coluna), SUM e AVG ignoram NULL. Já COUNT(*) conta a linha de qualquer jeito.
SELECT COUNT(*) AS linhas, COUNT(telefone) AS com_telefone FROM clientes;Versões de banco específico do COALESCE de dois argumentos.
SELECT IFNULL(telefone, 'sem') FROM clientes; -- MySQL
SELECT ISNULL(telefone, 'sem') FROM clientes; -- SQL Server
SELECT NVL(telefone, 'sem') FROM clientes; -- OracleTesta vários campos na ordem e devolve o primeiro preenchido.
SELECT COALESCE(celular, telefone, email, 'sem contato') AS contato FROM clientes;NULLIF transforma o zero em NULL, e o resultado vira NULL em vez de erro.
SELECT receita / NULLIF(pedidos, 0) AS ticket FROM metricas;COALESCE dentro do ORDER BY define onde os nulos caem.
SELECT * FROM produtos ORDER BY COALESCE(desconto, 0) DESC;Deixa explícito o que fazer com o dado ausente no relatório.
SELECT nome,
CASE WHEN telefone IS NULL THEN 'sem contato' ELSE telefone END AS contato
FROM clientes;O banco converte tipos sozinho às vezes — e isso derruba índice e esconde bug. Seja explícito.
-- Evite
SELECT * FROM pedidos WHERE id = '42';
-- Prefira
SELECT * FROM pedidos WHERE id = 42;Devolve NULL em vez de erro quando o valor não converte (SQL Server, PostgreSQL 16+).
SELECT TRY_CAST(codigo AS INTEGER) AS numero FROM importacao;Valor usado quando o INSERT não informa a coluna.
CREATE TABLE pedidos (
id INTEGER PRIMARY KEY,
status VARCHAR(20) DEFAULT 'pendente',
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Impede que a coluna fique vazia. Regra de negócio garantida pelo banco, não pela aplicação.
ALTER TABLE clientes ALTER COLUMN email SET NOT NULL;Em quase todos os bancos são coisas diferentes — no Oracle, string vazia vira NULL.
SELECT * FROM clientes WHERE telefone = ''; -- vazio
SELECT * FROM clientes WHERE telefone IS NULL; -- ausenteCombina COUNT(*) com COUNT(coluna) para medir a completude do dado.
SELECT COUNT(*) - COUNT(telefone) AS sem_telefone FROM clientes;Transformar milhares de linhas em um número: o coração de todo relatório.
Conta todas as linhas, inclusive as com colunas nulas.
SELECT COUNT(*) AS total FROM clientes;Conta apenas as linhas em que a coluna não é NULL.
SELECT COUNT(telefone) AS com_telefone FROM clientes;Conta valores diferentes — quantos clientes únicos compraram, por exemplo.
SELECT COUNT(DISTINCT cliente_id) AS clientes FROM pedidos;Soma os valores de uma coluna numérica.
SELECT SUM(total) AS faturamento FROM pedidos;Média aritmética, ignorando nulos.
SELECT ROUND(AVG(total), 2) AS ticket_medio FROM pedidos;Menor e maior valor. Funciona com número, texto e data.
SELECT MIN(criado_em) AS primeiro, MAX(criado_em) AS ultimo FROM pedidos;Calcula a agregação por categoria em vez de para a tabela toda.
SELECT cidade, COUNT(*) AS clientes
FROM clientes GROUP BY cidade;Cria um grupo para cada combinação distinta das colunas.
SELECT estado, cidade, COUNT(*)
FROM clientes GROUP BY estado, cidade;Toda coluna do SELECT que não está dentro de uma agregação precisa estar no GROUP BY.
-- Erro: nome não está agregado nem agrupado
-- SELECT cidade, nome, COUNT(*) FROM clientes GROUP BY cidade;
SELECT cidade, COUNT(*) FROM clientes GROUP BY cidade;É o WHERE dos grupos: roda depois da agregação.
SELECT cidade, COUNT(*) AS total
FROM clientes GROUP BY cidade
HAVING COUNT(*) > 10;WHERE filtra linhas antes de agrupar (mais rápido); HAVING filtra o resultado da agregação.
SELECT cidade, SUM(total) AS faturamento
FROM pedidos
WHERE status = 'pago' -- antes de agrupar
GROUP BY cidade
HAVING SUM(total) > 10000; -- depois de agruparRanking direto: agrupa, soma e ordena pelo resultado.
SELECT cidade, SUM(total) AS faturamento
FROM pedidos GROUP BY cidade
ORDER BY faturamento DESC LIMIT 10;Você pode agrupar por um cálculo, como o mês da data.
SELECT DATE_TRUNC('month', criado_em) AS mes, SUM(total)
FROM pedidos GROUP BY 1 ORDER BY 1;CASE dentro do SUM conta apenas o que interessa — várias métricas numa consulta só.
SELECT
COUNT(*) AS total,
SUM(CASE WHEN status = 'pago' THEN 1 ELSE 0 END) AS pagos,
SUM(CASE WHEN status = 'cancelado' THEN 1 ELSE 0 END) AS cancelados
FROM pedidos;Versão mais limpa da contagem condicional (PostgreSQL, SQLite).
SELECT COUNT(*) AS total,
COUNT(*) FILTER (WHERE status = 'pago') AS pagos
FROM pedidos;Divide a soma do grupo pela soma geral.
SELECT cidade,
ROUND(100.0 * SUM(total) / (SELECT SUM(total) FROM pedidos), 1) AS pct
FROM pedidos GROUP BY cidade ORDER BY pct DESC;Transforma linhas em colunas — meses lado a lado, por exemplo.
SELECT produto,
SUM(CASE WHEN mes = 1 THEN qtd ELSE 0 END) AS jan,
SUM(CASE WHEN mes = 2 THEN qtd ELSE 0 END) AS fev
FROM vendas GROUP BY produto;Junta os valores do grupo numa lista legível.
SELECT pedido_id, STRING_AGG(produto, ', ' ORDER BY produto) AS itens
FROM itens_pedido GROUP BY pedido_id;Comandos que mudam dados. Todos merecem um SELECT de conferência antes.
Sempre liste as colunas: se a tabela mudar, sua query continua funcionando.
INSERT INTO clientes (nome, email, cidade)
VALUES ('Ana Souza', 'ana@email.com', 'Campinas');Muito mais rápido que um INSERT por linha: é uma única ida ao banco.
INSERT INTO clientes (nome, email) VALUES
('Ana', 'ana@email.com'),
('Bruno', 'bruno@email.com'),
('Carla', 'carla@email.com');Copia dados de uma consulta para outra tabela. Base de carga e arquivamento.
INSERT INTO clientes_inativos (id, nome, email)
SELECT id, nome, email FROM clientes WHERE ultimo_acesso < CURRENT_DATE - INTERVAL '1 year';Deixa o banco preencher a coluna com o valor padrão.
INSERT INTO pedidos (cliente_id, status) VALUES (1, DEFAULT);Retorna a linha gravada, com id gerado, sem precisar de um SELECT depois (PostgreSQL, Oracle, Firebird).
INSERT INTO clientes (nome) VALUES ('Ana') RETURNING id, criado_em;Nunca esqueça o WHERE. Sem ele, você atualiza a tabela inteira.
UPDATE clientes SET cidade = 'Rio de Janeiro' WHERE id = 42;Separe as atribuições por vírgula.
UPDATE produtos SET preco = 99.90, atualizado_em = CURRENT_TIMESTAMP WHERE id = 7;O novo valor pode ser calculado a partir do valor atual.
UPDATE produtos SET preco = preco * 1.10 WHERE categoria = 'games';Busca o novo valor em outra tabela.
UPDATE pedidos ped
SET cliente_nome = (SELECT nome FROM clientes cli WHERE cli.id = ped.cliente_id);Atualiza usando JOIN — mais legível e rápido que subconsulta (PostgreSQL, SQL Server).
UPDATE pedidos ped
SET cliente_nome = cli.nome
FROM clientes cli
WHERE cli.id = ped.cliente_id;Mesma regra do UPDATE: sem WHERE, apaga tudo.
DELETE FROM clientes WHERE id = 42;Apaga com base em outra tabela.
DELETE FROM pedidos
WHERE cliente_id IN (SELECT id FROM clientes WHERE bloqueado);Muito mais rápido que DELETE sem WHERE, mas não dispara trigger e nem sempre dá para desfazer.
TRUNCATE TABLE log_acessos;Grava se não existe, atualiza se já existe. Cada banco tem sua sintaxe.
-- PostgreSQL / SQLite
INSERT INTO metricas (dia, acessos) VALUES (CURRENT_DATE, 1)
ON CONFLICT (dia) DO UPDATE SET acessos = metricas.acessos + 1;
-- MySQL
INSERT INTO metricas (dia, acessos) VALUES (CURRENT_DATE, 1)
ON DUPLICATE KEY UPDATE acessos = acessos + 1;Rode o WHERE em um SELECT primeiro. Dois segundos que evitam um restore de backup.
-- 1) Confira o que será afetado
SELECT * FROM clientes WHERE cidade = 'Campinas';
-- 2) Só então execute
UPDATE clientes SET ativo = false WHERE cidade = 'Campinas';Modelar bem no começo evita meses de gambiarra depois. Restrição no banco vale mais que validação na aplicação.
Define colunas, tipos e regras da tabela.
CREATE TABLE clientes (
id INTEGER PRIMARY KEY,
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) UNIQUE,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);SMALLINT, INTEGER e BIGINT mudam o intervalo e o espaço ocupado. Id de tabela grande merece BIGINT.
CREATE TABLE eventos (
id BIGINT,
tentativas SMALLINT
);DECIMAL(p, s) é exato: p dígitos no total, s depois da vírgula. Use para dinheiro.
CREATE TABLE pedidos (total DECIMAL(12,2));VARCHAR(n) limita o tamanho; TEXT é livre. Limite quando o tamanho for regra de negócio.
CREATE TABLE posts (
titulo VARCHAR(200) NOT NULL,
conteudo TEXT
);DATE (só data), TIME (só hora), TIMESTAMP (as duas). Prefira timestamp com fuso.
CREATE TABLE agenda (
dia DATE,
inicio TIME,
criado_em TIMESTAMP WITH TIME ZONE
);BOOLEAN no PostgreSQL; MySQL usa TINYINT(1); Oracle e Firebird antigos, CHAR(1) ou SMALLINT.
CREATE TABLE clientes (ativo BOOLEAN DEFAULT TRUE);Identificador único global. Bom para sistemas distribuídos; ocupa mais que um inteiro.
CREATE TABLE sessoes (id UUID PRIMARY KEY);Guarda estrutura variável dentro de uma coluna. No PostgreSQL prefira JSONB (indexável).
CREATE TABLE configuracoes (
id INTEGER PRIMARY KEY,
dados JSONB NOT NULL DEFAULT '{}'
);Garante que a coluna sempre tenha valor.
CREATE TABLE clientes (nome VARCHAR(120) NOT NULL);Valor assumido quando o INSERT omite a coluna.
CREATE TABLE pedidos (status VARCHAR(20) NOT NULL DEFAULT 'pendente');Identifica cada linha de forma única e cria um índice automaticamente.
CREATE TABLE produtos (id INTEGER PRIMARY KEY, nome VARCHAR(120));Duas ou mais colunas juntas identificam a linha. Comum em tabelas de ligação.
CREATE TABLE pedido_produto (
pedido_id INTEGER,
produto_id INTEGER,
PRIMARY KEY (pedido_id, produto_id)
);Cada banco tem sua forma de gerar o próximo id.
-- PostgreSQL
CREATE TABLE a (id SERIAL PRIMARY KEY);
CREATE TABLE b (id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY);
-- MySQL
CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY);
-- SQL Server
CREATE TABLE d (id INT IDENTITY(1,1) PRIMARY KEY);Impede valores repetidos na coluna. Um e-mail por cliente, por exemplo.
ALTER TABLE clientes ADD CONSTRAINT uk_clientes_email UNIQUE (email);Valida o valor no próprio banco — a regra vale para qualquer aplicação que escrever ali.
ALTER TABLE produtos ADD CONSTRAINT ck_preco_positivo CHECK (preco > 0);Garante que o valor existe na tabela referenciada. É o que mantém a integridade do modelo.
ALTER TABLE pedidos
ADD CONSTRAINT fk_pedidos_cliente FOREIGN KEY (cliente_id) REFERENCES clientes (id);Apaga os filhos junto com o pai. Poderoso e perigoso: confirme que é o que você quer.
ALTER TABLE itens
ADD CONSTRAINT fk_itens_pedido FOREIGN KEY (pedido_id)
REFERENCES pedidos (id) ON DELETE CASCADE;Adiciona coluna à tabela existente. Com DEFAULT em tabela grande, verifique o bloqueio antes.
ALTER TABLE clientes ADD COLUMN telefone VARCHAR(20);Remove a coluna e os dados dela. Não tem volta sem backup.
ALTER TABLE clientes DROP COLUMN telefone;Renomeação é barata, mas quebra tudo que referencia o nome antigo.
ALTER TABLE clientes RENAME TO customers;
ALTER TABLE customers RENAME COLUMN nome TO name;Apaga estrutura e dados. IF EXISTS evita erro quando a tabela já não existe.
DROP TABLE IF EXISTS log_temporario;Cria uma tabela já preenchida com o resultado de uma consulta. Ótimo para snapshot antes de uma migração.
CREATE TABLE clientes_backup AS
SELECT * FROM clientes;