SQL para análise de dados · Guias · 30 de outubro de 2026 · 13 min de leitura
Cola de SQL (cheat sheet) para analista de dados: comandos e funções com exemplos

Este é o SQL que um analista usa no dia a dia, numa página só: as cláusulas de uma consulta, os filtros, as agregações, os JOIN, o CASE, o tratamento de NULL, subconsultas e CTE, funções de janela e as funções de data e de texto que você mais usa. Cada seção tem uma tabela de referência curta e um exemplo com a saída real, para você ver o que a sintaxe devolve de fato em vez de adivinhar.
Todos os exemplos rodam no banco de dados da companhia aérea do curso de SQL da Nomto: 104 aeroportos, 9 modelos de avião, 33.121 voos de 12 de julho a 10 de setembro de 2026, 262.788 reservas, valores em dólares americanos e horários no horário de Moscou. A sintaxe é a do PostgreSQL. Você pode colar qualquer consulta na sala de SQL da Nomto e executá-la como está.
As tabelas usadas abaixo
| Tabela | Uma linha é | Colunas usadas aqui |
|---|---|---|
airports |
um aeroporto | airport_code, airport_name, city, timezone |
aircrafts |
um modelo de avião | aircraft_code, model, range (km) |
flights |
uma partida num dia | flight_id, flight_no, scheduled_departure, actual_departure, departure_airport, arrival_airport, status, aircraft_code |
bookings |
uma reserva | book_ref, book_date, total_amount |
tickets |
um passageiro numa reserva | ticket_no, book_ref, passenger_name, contact_data (JSON) |
ticket_flights |
um trecho de voo num bilhete | ticket_no, flight_id, fare_conditions, amount |
Esqueleto da consulta: as cláusulas e a ordem delas
| Cláusula | O que faz | Executa |
|---|---|---|
SELECT |
colunas e expressões a devolver | 5ª |
FROM / JOIN |
de onde vêm as linhas | 1ª |
WHERE |
mantém as linhas que atendem a uma condição | 2ª |
GROUP BY |
uma linha por grupo | 3ª |
HAVING |
mantém os grupos que atendem a uma condição | 4ª |
ORDER BY |
ordena o resultado | 6ª |
LIMIT |
mantém as primeiras N linhas | 7ª |
Você escreve na ordem da primeira coluna da tabela, mas o banco executa na ordem da última. É por isso que um alias definido no SELECT funciona no ORDER BY, mas não no WHERE.
SELECT model, range
FROM aircrafts
WHERE range >= 5000
ORDER BY range DESC
LIMIT 3;
| model | range |
|---|---|
| Boeing 777-300 | 11100 |
| Boeing 767-300 | 7900 |
| Airbus A319-100 | 6700 |
Filtros (WHERE)
| Operador | Exemplo | Observação |
|---|---|---|
=, <>, <, >= |
range >= 5000 |
texto vai entre aspas simples |
AND, OR, NOT |
a AND (b OR c) |
AND tem precedência sobre OR: use parênteses |
IN |
status IN ('Departed', 'Arrived') |
atalho para vários OR |
BETWEEN |
x BETWEEN 1 AND 10 |
inclui as duas pontas |
LIKE / ILIKE |
city LIKE 'Novo%' |
% qualquer sequência, _ um caractere; ILIKE ignora maiúsculas e minúsculas |
IS NULL |
actual_departure IS NULL |
nunca = NULL |
SELECT COUNT(*) FILTER (WHERE status IN ('Departed', 'Arrived')) AS flown,
COUNT(*) FILTER (WHERE scheduled_departure BETWEEN '2026-08-01' AND '2026-08-07 23:59:59') AS first_week_aug,
COUNT(*) FILTER (WHERE actual_departure IS NULL) AS not_departed
FROM flights;
| flown | first_week_aug | not_departed |
|---|---|---|
| 16765 | 3798 | 16348 |
O FILTER (WHERE ...) permite colocar três filtros diferentes numa só consulta; cada COUNT só vê as próprias linhas. Para intervalos de datas, >= '2026-08-01' AND < '2026-08-08' é mais seguro que BETWEEN, porque você não precisa lembrar do 23:59:59.
Agregações
| Função | Devolve |
|---|---|
COUNT(*) |
número de linhas |
COUNT(col) |
número de valores não NULL |
COUNT(DISTINCT col) |
número de valores distintos não NULL |
SUM(col), AVG(col) |
total, média (NULL ignorados) |
MIN(col), MAX(col) |
menor, maior (funciona também com datas e texto) |
SELECT COUNT(*) AS bookings,
COUNT(DISTINCT DATE_TRUNC('day', book_date)) AS days,
SUM(total_amount) AS revenue,
ROUND(AVG(total_amount), 2) AS avg_booking,
MIN(total_amount) AS min_b,
MAX(total_amount) AS max_b
FROM bookings;
| bookings | days | revenue | avg_booking | min_b | max_b |
|---|---|---|---|---|---|
| 262788 | 56 | 207669809.00 | 790.26 | 34.00 | 12045.00 |
GROUP BY e HAVING
Toda coluna do SELECT precisa estar no GROUP BY ou dentro de uma agregação. O WHERE filtra linhas antes do agrupamento; o HAVING filtra grupos depois.
SELECT city, COUNT(*) AS airports
FROM airports
GROUP BY city
HAVING COUNT(*) > 1
ORDER BY airports DESC, city;
| city | airports |
|---|---|
| Moscow | 3 |
| Ulyanovsk | 2 |
Mais sobre a diferença em GROUP BY e HAVING.
JOIN
| JOIN | Mantém |
|---|---|
JOIN (INNER JOIN) |
só as linhas com correspondência dos dois lados |
LEFT JOIN |
todas as linhas da tabela da esquerda; NULL onde o lado direito não tem correspondência |
RIGHT JOIN |
o espelho do LEFT JOIN (pouco usado: inverta as tabelas) |
FULL JOIN |
todas as linhas dos dois lados |
CROSS JOIN |
todas as combinações de linhas |
LEFT JOIN ... WHERE direita.chave IS NULL |
linhas sem correspondência (um anti join) |
Buscar um valor em outra tabela:
SELECT f.flight_no, a.model
FROM flights f
JOIN aircrafts a ON a.aircraft_code = f.aircraft_code
WHERE f.flight_id = 1;
| flight_no | model |
|---|---|
| PG0405 | Airbus A321-200 |
Achar linhas sem par, aqui o modelo de avião que nunca voa:
SELECT a.model
FROM aircrafts a
LEFT JOIN flights f ON f.aircraft_code = a.aircraft_code
WHERE f.flight_id IS NULL;
| model |
|---|
| Airbus A320-200 |
A armadilha para não esquecer: quando uma linha encontra várias correspondências, ela é repetida uma vez para cada uma. Somar bookings.total_amount depois de juntar tickets dá 343.206.192 em vez dos 207.669.809 corretos, porque as reservas com vários bilhetes são contadas várias vezes. O guia de JOIN no SQL explica esse fan-out em detalhe.
CASE: lógica se-então
CASE WHEN condition1 THEN value1
WHEN condition2 THEN value2
ELSE default
END
As condições são testadas de cima para baixo e vence a primeira verdadeira, então coloque a mais restrita primeiro.
SELECT CASE WHEN total_amount >= 1000 THEN 'large'
WHEN total_amount >= 300 THEN 'medium'
ELSE 'small'
END AS size,
COUNT(*) AS bookings
FROM bookings
GROUP BY size
ORDER BY bookings DESC;
| size | bookings |
|---|---|
| medium | 130009 |
| small | 68305 |
| large | 64474 |
SUM(CASE WHEN ... THEN 1 ELSE 0 END) é o jeito portável de contar por condição; COUNT(*) FILTER (WHERE ...) faz o mesmo no PostgreSQL.
Tratamento de NULL
| Expressão | O que faz |
|---|---|
col IS NULL / IS NOT NULL |
o único teste correto para NULL |
COALESCE(a, b, ...) |
o primeiro valor não NULL |
NULLIF(a, b) |
NULL se a = b, senão a (útil para evitar divisão por zero) |
COUNT(col) |
ignora NULL; COUNT(*) não ignora |
SELECT flight_no, scheduled_departure, status,
COALESCE(CAST(actual_departure AS VARCHAR), 'not departed') AS actual
FROM flights
WHERE flight_no = 'PG0405'
AND scheduled_departure BETWEEN '2026-08-10' AND '2026-08-13'
ORDER BY scheduled_departure;
| flight_no | scheduled_departure | status | actual |
|---|---|---|---|
| PG0405 | 2026-08-10 09:35:00 | Arrived | 2026-08-10 09:37:00 |
| PG0405 | 2026-08-11 09:35:00 | Arrived | 2026-08-11 09:38:00 |
| PG0405 | 2026-08-12 09:35:00 | On Time | not departed |
Uma porcentagem segura, que devolve NULL em vez de erro se a tabela estiver vazia:
SELECT ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'Cancelled') / NULLIF(COUNT(*), 0), 2) AS cancelled_pct
FROM flights;
| cancelled_pct |
|---|
| 1.25 |
Escreva 100.0, não 100: com dois inteiros, alguns bancos fazem divisão inteira e devolvem 0.
Subconsultas e CTE
Uma subconsulta é uma consulta dentro de outra. Reservas acima da média:
SELECT COUNT(*) AS above_avg
FROM bookings
WHERE total_amount > (SELECT AVG(total_amount) FROM bookings);
| above_avg |
|---|
| 87224 |
Uma CTE (WITH) dá nome a uma subconsulta para que você leia a consulta de cima para baixo. Voos por dia e, depois, o mínimo, a média e o máximo dessas contagens diárias:
WITH daily AS (
SELECT CAST(scheduled_departure AS DATE) AS day, COUNT(*) AS flights
FROM flights
GROUP BY day
)
SELECT MIN(flights) AS min_day, ROUND(AVG(flights), 1) AS avg_day, MAX(flights) AS max_day
FROM daily;
| min_day | avg_day | max_day |
|---|---|---|
| 493 | 543 | 568 |
Use uma CTE sempre que precisar agregar duas vezes (a média de contagens diárias, a fatia de um total) ou quando a consulta ficar maior que uma tela.
Funções de janela
Uma função de janela acrescenta um valor a cada linha sem juntar as linhas. O formato é função() OVER (PARTITION BY ... ORDER BY ... frame).
| Função | Use para |
|---|---|
ROW_NUMBER() |
top N por grupo, remover duplicatas |
RANK(), DENSE_RANK() |
rankings com empates (RANK pula posições, DENSE_RANK não) |
LAG(col), LEAD(col) |
valor da linha anterior / seguinte |
SUM(col) OVER (ORDER BY ...) |
total acumulado |
AVG(col) OVER (ORDER BY ... ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) |
média móvel de 7 linhas |
SUM(col) OVER () |
total geral ao lado de cada linha (para fatias) |
Comparação com o dia anterior e total acumulado numa só consulta:
SELECT CAST(book_date AS DATE) AS day,
COUNT(*) AS bookings,
LAG(COUNT(*)) OVER (ORDER BY CAST(book_date AS DATE)) AS prev_day,
SUM(COUNT(*)) OVER (ORDER BY CAST(book_date AS DATE)) AS running_total
FROM bookings
GROUP BY day
ORDER BY day
LIMIT 4;
| day | bookings | prev_day | running_total |
|---|---|---|---|
| 2026-06-17 | 3 | NULL | 3 |
| 2026-06-18 | 7 | 3 | 10 |
| 2026-06-19 | 20 | 7 | 30 |
| 2026-06-20 | 74 | 20 | 104 |
Top 1 por grupo com ROW_NUMBER: o aeroporto de partida mais movimentado de cada modelo de avião.
SELECT *
FROM (
SELECT a.model, f.departure_airport, COUNT(*) AS flights,
ROW_NUMBER() OVER (PARTITION BY a.model ORDER BY COUNT(*) DESC) AS rn
FROM flights f
JOIN aircrafts a USING (aircraft_code)
GROUP BY a.model, f.departure_airport
) t
WHERE rn = 1
ORDER BY flights DESC
LIMIT 4;
| model | departure_airport | flights | rn |
|---|---|---|---|
| Sukhoi Superjet-100 | SVO | 1011 | 1 |
| Bombardier CRJ-200 | DME | 829 | 1 |
| Airbus A321-200 | LED | 793 | 1 |
| Cessna 208 Caravan | KJA | 488 | 1 |
Não dá para filtrar por uma função de janela no WHERE da mesma consulta, daí a subconsulta. Passo a passo completo: funções de janela SQL e running totals (em inglês).
Datas e horas
| Expressão | Resultado |
|---|---|
CAST(ts AS DATE) |
descarta a hora |
DATE_TRUNC('month', ts) |
primeiro instante do mês (também 'week', 'day', 'hour') |
EXTRACT(MONTH FROM ts) |
número do mês; também YEAR, DAY, HOUR, ISODOW (1 = segunda-feira) |
ts + INTERVAL '30 days' |
aritmética de datas |
date1 - date2 |
dias entre duas datas |
EXTRACT(EPOCH FROM ts1 - ts2) / 60 |
minutos entre dois timestamps |
Voos por mês:
SELECT DATE_TRUNC('month', scheduled_departure) AS month, COUNT(*) AS flights
FROM flights
GROUP BY month
ORDER BY month;
| month | flights |
|---|---|
| 2026-07-01 00:00:00 | 10826 |
| 2026-08-01 00:00:00 | 16854 |
| 2026-09-01 00:00:00 | 5441 |
Julho e setembro são meses incompletos aqui (a malha vai de 12 de julho a 10 de setembro), algo que vale a pena dizer em voz alta sempre que você compara meses.
Somar e subtrair datas:
SELECT book_ref, book_date,
book_date + INTERVAL '30 days' AS plus_30,
'2026-08-11'::DATE - CAST(book_date AS DATE) AS days_ago
FROM bookings
ORDER BY book_ref
LIMIT 2;
| book_ref | book_date | plus_30 | days_ago |
|---|---|---|---|
| 00000F | 2026-07-01 03:12:00 | 2026-07-31 03:12:00 | 41 |
| 000012 | 2026-07-10 09:02:00 | 2026-08-09 09:02:00 | 32 |
Os maiores atrasos de partida, em minutos:
SELECT flight_no, scheduled_departure, actual_departure,
ROUND(EXTRACT(EPOCH FROM actual_departure - scheduled_departure) / 60) AS delay_min
FROM flights
WHERE status = 'Arrived'
ORDER BY delay_min DESC
LIMIT 3;
| flight_no | scheduled_departure | actual_departure | delay_min |
|---|---|---|---|
| PG0589 | 2026-07-25 15:30:00 | 2026-07-25 20:07:00 | 277 |
| PG0164 | 2026-07-25 15:25:00 | 2026-07-25 19:53:00 | 268 |
| PG0364 | 2026-07-15 11:45:00 | 2026-07-15 16:12:00 | 267 |
Para dias sem nenhuma linha (um dia com zero reservas simplesmente não aparece no GROUP BY), veja o guia de date spine (em inglês).
Textos
| Função | Exemplo | Resultado |
|---|---|---|
|| |
'PG' || '0405' |
PG0405 |
UPPER, LOWER |
LOWER('SVO') |
svo |
LENGTH |
LENGTH('Vnukovo') |
7 |
TRIM |
TRIM(' SVO ') |
SVO |
LEFT(s, n), SUBSTRING(s, start, len) |
LEFT('Moscow', 3) |
Mos |
POSITION(sub IN s) |
POSITION('/' IN 'Europe/Moscow') |
7 |
REPLACE(s, from, to) |
REPLACE('Asia/Ust-Nera', '-', ' ') |
Asia/Ust Nera |
SPLIT_PART(s, sep, n) |
SPLIT_PART('IVAN PETROV', ' ', 2) |
PETROV |
json ->> 'key' |
contact_data ->> 'phone' |
o valor como texto |
Os nomes dos passageiros estão gravados em maiúsculas no formato “NOME SOBRENOME”, e os contatos são JSON:
SELECT passenger_name,
LOWER(SPLIT_PART(passenger_name, ' ', 2)) AS last_lower,
contact_data ->> 'phone' AS phone
FROM tickets
ORDER BY ticket_no
LIMIT 3;
| passenger_name | last_lower | phone |
|---|---|---|
| VALERIY TIKHONOV | tikhonov | +70127117011 |
| EVGENIYA ALEKSEEVA | alekseeva | +70378089255 |
| ARTUR GERASIMOV | gerasimov | +70760429203 |
Montar um rótulo e cortar uma string num caractere:
SELECT airport_name || ' (' || airport_code || ')' AS label,
SUBSTRING(timezone, 1, POSITION('/' IN timezone) - 1) AS region
FROM airports
WHERE city = 'Moscow'
ORDER BY airport_code;
| label | region |
|---|---|
| Domodedovo (DME) | Europe |
| Sheremetyevo (SVO) | Europe |
| Vnukovo (VKO) | Europe |
Todas as funções de texto, com mais exemplos, estão em funções de texto no SQL.
Erros que esta cola evita
= NULL. SempreIS NULL; a igualdade não devolve nenhuma linha e nenhum erro.LIMITsemORDER BY. Você recebe algumas linhas, não as primeiras do ranking.- Somar depois de um JOIN de um para muitos. Agregue o lado “muitos” primeiro e depois junte.
- Divisão inteira.
100 * a / bpode arredondar para 0; escreva100.0. - Filtrar uma função de janela no
WHERE. Coloque-a numa subconsulta ou CTE. BETWEENcom timestamps.'2026-08-07'significa meia-noite; use>=no início e<no dia seguinte.
Diferenças entre dialetos
Tudo aqui é PostgreSQL. A maior parte é igual em outros bancos; as diferenças mais comuns são:
| Tarefa | PostgreSQL | MySQL | SQL Server |
|---|---|---|---|
| primeiras N linhas | LIMIT 10 |
LIMIT 10 |
TOP 10 / FETCH FIRST 10 ROWS ONLY |
| concatenar | a || b |
CONCAT(a, b) |
a + b ou CONCAT(a, b) |
| truncar uma data | DATE_TRUNC('month', d) |
DATE_FORMAT(d, '%Y-%m-01') |
DATETRUNC(month, d) (2022 em diante) |
| LIKE sem distinção de caixa | ILIKE |
LIKE (depende da collation) |
LIKE (depende da collation) |
| contagem condicional | COUNT(*) FILTER (WHERE ...) |
SUM(CASE WHEN ... THEN 1 ELSE 0 END) |
SUM(CASE WHEN ... THEN 1 ELSE 0 END) |
Como usar esta cola
Deixe-a aberta enquanto trabalha, mas não tente decorá-la. A sintaxe fica na memória quando você a usa em perguntas que importam para você. Na sala de SQL da Nomto, todas as consultas acima rodam no mesmo banco da companhia aérea, então mude um filtro ou uma coluna de agrupamento e veja o que muda. Se você está começando, comandos SQL básicos explica a primeira metade desta página passo a passo, e os exercícios de SQL com respostas trazem tarefas para aplicar a segunda metade.
Perguntas frequentes
Que SQL um analista de dados precisa saber?
Com segurança: SELECT, WHERE, GROUP BY, HAVING, JOIN, CASE, tratamento de NULL, subconsultas e CTE, e funções de data. Depois, funções de janela (ROW_NUMBER, LAG, totais acumulados), que aparecem na maioria das entrevistas para analista. Índices, stored procedures e administração de banco importam bem menos para essa função.
Esta cola vale para MySQL e SQL Server?
As cláusulas, os JOIN, as agregações, o CASE, o COALESCE, as subconsultas, as CTE e as funções de janela funcionam do mesmo jeito. Funções de data, concatenação de texto e LIMIT mudam; a tabela de dialetos acima lista os casos comuns.
Qual a diferença entre subconsulta e CTE?
Fazem o mesmo trabalho. A CTE dá um nome à subconsulta e a leva para o topo, então uma consulta longa é lida em etapas. Use a que for mais fácil de ler; para qualquer coisa com duas etapas ou mais, normalmente é a CTE.
Dá para executar estes exemplos sem instalar um banco de dados?
Sim. Abra a sala de SQL da Nomto, cole uma consulta e execute no navegador, nos mesmos dados da companhia aérea.
Outros idiomas: English, Español, Tiếng Việt, 简体中文