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

Uma mesa com uma referência de SQL impressa em uma página, um notebook mostrando uma consulta e uma xícara de café

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

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, 简体中文