SQL para análise de dados · Guias · 30 de outubro de 2026 · 11 min de leitura
Comandos SQL básicos: SELECT, WHERE, ORDER BY, GROUP BY e JOIN para iniciantes

Uma consulta SQL básica tem sempre a mesma forma: SELECT diz quais colunas você quer ver, FROM de qual tabela elas vêm, WHERE filtra as linhas, GROUP BY agrupa as linhas para calcular totais, ORDER BY ordena o resultado e JOIN combina duas tabelas por uma coluna em comum. Com esses seis comandos você resolve a maior parte do trabalho diário de um analista. Este guia passa por cada um deles com consultas que funcionam de verdade: todas rodam no banco de dados de uma companhia aérea usado no curso de SQL da Nomto, e cada resultado mostrado abaixo é a saída real.
O banco de dados dos exemplos
Vamos trabalhar com cinco tabelas de uma companhia aérea (os textos dos dados estão em inglês, os valores em dólares americanos e os horários no horário-base da companhia, Moscou, UTC+3):
| Tabela | Uma linha é | Colunas que vamos usar |
|---|---|---|
airports |
um aeroporto (104) | airport_code, airport_name, city, timezone |
aircrafts |
um modelo de avião (9) | aircraft_code, model, range (alcance em km) |
flights |
uma partida de um voo num dia (33.121) | flight_no, scheduled_departure, departure_airport, arrival_airport, status, aircraft_code, actual_departure |
bookings |
uma reserva | book_ref, book_date, total_amount |
ticket_flights |
um trecho de voo num bilhete | flight_id, fare_conditions (classe), amount (preço) |
Antes de escrever qualquer consulta, vale saber o que representa uma linha de cada tabela. Quase todos os erros de iniciante em SQL vêm de contar linhas que não são o que a gente imagina.
SELECT e FROM: escolher colunas
A consulta mais simples pede colunas de uma tabela:
SELECT model, range
FROM aircrafts;
Algumas regras que vale fixar desde o começo:
SELECT *devolve todas as colunas. Serve para dar uma olhada, mas numa consulta de verdade liste as colunas de que você precisa: o resultado fica mais claro e não quebra se alguém acrescentar colunas à tabela.- No
SELECTvocê pode fazer cálculos. ComASvocê dá um nome à coluna calculada. - O ponto e vírgula no final separa consultas. Na sala de SQL da Nomto roda uma consulta por vez, então você pode omiti-lo.
O alcance está em quilômetros. Se quisermos também em milhas:
SELECT model,
range,
ROUND(range / 1.609) AS range_miles
FROM aircrafts
ORDER BY range DESC
LIMIT 5;
| model | range | range_miles |
|---|---|---|
| Boeing 777-300 | 11100 | 6899 |
| Boeing 767-300 | 7900 | 4910 |
| Airbus A319-100 | 6700 | 4164 |
| Airbus A320-200 | 5700 | 3543 |
| Airbus A321-200 | 5600 | 3480 |
Sem ROUND, a coluna sairia cheia de casas decimais (6.898,69484151647). ORDER BY e LIMIT aparecem mais abaixo.
WHERE: filtrar linhas
O WHERE deixa passar só as linhas que atendem a uma condição. Os aeroportos de Moscou:
SELECT airport_code, airport_name, city
FROM airports
WHERE city = 'Moscow'
ORDER BY airport_code;
| airport_code | airport_name | city |
|---|---|---|
| DME | Domodedovo | Moscow |
| SVO | Sheremetyevo | Moscow |
| VKO | Vnukovo | Moscow |
Repare em dois detalhes: o texto vai entre aspas simples (no PostgreSQL as aspas duplas são para nomes de colunas) e a comparação diferencia maiúsculas de minúsculas: 'moscow' não devolve nada.
Combinar condições: AND, OR, IN, BETWEEN
As partidas de Sheremetyevo em 1º de agosto:
SELECT flight_no, scheduled_departure, arrival_airport, status
FROM flights
WHERE departure_airport = 'SVO'
AND scheduled_departure >= '2026-08-01'
AND scheduled_departure < '2026-08-02'
ORDER BY scheduled_departure, flight_no
LIMIT 5;
| flight_no | scheduled_departure | arrival_airport | status |
|---|---|---|---|
| PG0120 | 2026-08-01 09:00:00 | MJZ | Arrived |
| PG0591 | 2026-08-01 09:00:00 | TOF | Arrived |
| PG0241 | 2026-08-01 09:40:00 | CSY | Arrived |
| PG0677 | 2026-08-01 09:40:00 | MCX | Arrived |
| PG0041 | 2026-08-01 09:50:00 | NFG | Arrived |
Nesse dia saíram 50 voos de SVO. O filtro de datas segue o padrão “maior ou igual ao início do dia e menor que o início do dia seguinte”. É mais seguro que BETWEEN '2026-08-01' AND '2026-08-01', que em colunas de data e hora só pega o instante exato da meia-noite.
O IN substitui vários OR sobre a mesma coluna. Os voos que já decolaram (no ar ou já pousados):
SELECT COUNT(*)
FROM flights
WHERE status IN ('Departed', 'Arrived');
O resultado é 16.765. Quando misturar AND e OR, use parênteses: o AND é avaliado primeiro, e a OR b AND c significa a OR (b AND c).
LIKE: buscar por padrão
% substitui qualquer quantidade de caracteres e _ substitui exatamente um. As cidades que começam com “Novo”:
SELECT airport_code, airport_name, city
FROM airports
WHERE city LIKE 'Novo%'
ORDER BY city;
| airport_code | airport_name | city |
|---|---|---|
| NOZ | Spichenkovo | Novokuznetsk |
| OVB | Tolmachevo | Novosibirsk |
No PostgreSQL, o ILIKE faz o mesmo sem diferenciar maiúsculas de minúsculas.
IS NULL: valores vazios
NULL significa “sem valor”. Um voo que ainda não saiu tem actual_departure vazio. Aqui está a armadilha mais famosa do SQL:
SELECT COUNT(*) FROM flights WHERE actual_departure = NULL; -- 0
SELECT COUNT(*) FROM flights WHERE actual_departure IS NULL; -- 16348
A primeira devolve 0, embora existam milhares de voos que ainda não saíram: qualquer comparação com NULL dá “desconhecido”, e o WHERE só deixa passar o que é verdadeiro. Para valores vazios, use sempre IS NULL ou IS NOT NULL.
ORDER BY e LIMIT: ordenar e ficar com as primeiras linhas
Sem ORDER BY, o banco de dados devolve as linhas na ordem que for mais conveniente para ele, e essa ordem pode mudar de uma execução para outra. As cinco reservas mais caras:
SELECT book_ref, book_date, total_amount
FROM bookings
ORDER BY total_amount DESC
LIMIT 5;
| book_ref | book_date | total_amount |
|---|---|---|
| 3B54BB | 2026-07-01 17:08:00 | 12045.00 |
| 3AC131 | 2026-07-27 01:06:00 | 10871.00 |
| 65A6EA | 2026-06-29 06:28:00 | 10656.00 |
| D7E9AA | 2026-08-04 05:29:00 | 10628.00 |
| EF479E | 2026-07-29 15:58:00 | 10351.00 |
ASC(o padrão) ordena do menor para o maior;DESC, do maior para o menor.- Você pode ordenar por várias colunas:
ORDER BY scheduled_departure, flight_nodesempata pelo número do voo quando dois saem no mesmo horário, como no exemplo de SVO. LIMITsemORDER BYdevolve “algumas linhas quaisquer”, não “as primeiras”. Se importa quais linhas aparecem, ordene.
Funções de agregação: COUNT, SUM, AVG, MIN, MAX
As funções de agregação transformam muitas linhas num único número. Uma diferença que vale entender logo cedo:
SELECT COUNT(*) AS total,
COUNT(actual_departure) AS departed,
COUNT(*) - COUNT(actual_departure) AS not_yet
FROM flights;
| total | departed | not_yet |
|---|---|---|
| 33121 | 16773 | 16348 |
COUNT(*) conta linhas; COUNT(coluna) conta só os valores não vazios. COUNT(DISTINCT coluna) conta valores distintos: SELECT COUNT(DISTINCT timezone) FROM airports devolve 17 fusos horários.
GROUP BY: um total por grupo
O GROUP BY divide a tabela em grupos e calcula a agregação dentro de cada um. Quantos voos existem em cada status:
SELECT status, COUNT(*) AS flights
FROM flights
GROUP BY status
ORDER BY flights DESC;
| status | flights |
|---|---|
| Arrived | 16707 |
| Scheduled | 15383 |
| On Time | 518 |
| Cancelled | 414 |
| Departed | 58 |
| Delayed | 41 |
Várias agregações de uma vez, por classe tarifária:
SELECT fare_conditions,
COUNT(*) AS segments,
ROUND(AVG(amount), 2) AS avg_price,
MIN(amount) AS min_price,
MAX(amount) AS max_price
FROM ticket_flights
GROUP BY fare_conditions
ORDER BY avg_price DESC;
| fare_conditions | segments | avg_price | min_price | max_price |
|---|---|---|---|---|
| Business | 107642 | 511.43 | 91.00 | 2033.00 |
| Comfort | 17291 | 327.41 | 199.00 | 476.00 |
| Economy | 920793 | 159.6 | 30.00 | 745.00 |
Em média, um trecho na executiva custa $511,43, e na econômica, $159,60. A regra de ouro: toda coluna do SELECT precisa estar no GROUP BY ou dentro de uma função de agregação. Se você escrever SELECT status, flight_no, COUNT(*) ... GROUP BY status, o PostgreSQL dá erro, porque num grupo há centenas de números de voo e ele não sabe qual mostrar.
HAVING: filtrar grupos
O WHERE filtra linhas antes de agrupar; o HAVING filtra grupos depois. As cidades com mais de um aeroporto:
SELECT city, COUNT(*) AS airports
FROM airports
GROUP BY city
HAVING COUNT(*) > 1
ORDER BY airports DESC;
| city | airports |
|---|---|
| Moscow | 3 |
| Ulyanovsk | 2 |
Não dá para escrever WHERE COUNT(*) > 1: quando o WHERE é avaliado, os grupos ainda não existem. A diferença entre os dois está detalhada no guia de GROUP BY e HAVING.
JOIN: combinar tabelas
Na tabela flights, o avião aparece só como código (321, SU9). O nome do modelo está em aircrafts. O JOIN une as duas tabelas pela coluna em comum:
SELECT a.model, COUNT(*) AS flights
FROM flights f
JOIN aircrafts a ON a.aircraft_code = f.aircraft_code
GROUP BY a.model
ORDER BY flights DESC;
| model | flights |
|---|---|
| Cessna 208 Caravan | 9273 |
| Bombardier CRJ-200 | 9048 |
| Sukhoi Superjet-100 | 8504 |
| Airbus A321-200 | 1952 |
| Boeing 737-300 | 1274 |
| Airbus A319-100 | 1239 |
| Boeing 767-300 | 1221 |
| Boeing 777-300 | 610 |
f e a são aliases: nomes curtos para não repetir o nome inteiro da tabela. O ON diz quais linhas andam juntas.
INNER JOIN e LEFT JOIN
A tabela acima tem 8 modelos, mas em aircrafts há 9. O JOIN (que é o INNER JOIN) fica só com as linhas que têm par nas duas tabelas, então o modelo sem voos some. O LEFT JOIN mantém todas as linhas da tabela da esquerda e preenche com NULL o que não encontra. É assim que se procuram “linhas sem par”:
SELECT a.aircraft_code, a.model
FROM aircrafts a
LEFT JOIN flights f ON f.aircraft_code = a.aircraft_code
WHERE f.flight_id IS NULL;
| aircraft_code | model |
|---|---|
| 320 | Airbus A320-200 |
Se for contar com LEFT JOIN, use COUNT(f.flight_id) e não COUNT(*): assim o A320 aparece com 0, e não com 1.
Um aviso para quando você avançar: se você junta uma tabela com outra que tem várias linhas para cada linha da primeira (uma reserva com vários bilhetes), as linhas se multiplicam e as somas ficam infladas. Explicamos isso com números no guia de JOIN no SQL e nas perguntas de entrevista SQL.
Tudo junto: uma consulta completa
Os comandos se combinam numa ordem fixa: SELECT … FROM … JOIN … WHERE … GROUP BY … HAVING … ORDER BY … LIMIT. Cidades com pelo menos 300 voos já pousados e o atraso médio de partida, em minutos:
SELECT ap.city,
COUNT(*) AS flights,
ROUND(AVG(EXTRACT(EPOCH FROM f.actual_departure - f.scheduled_departure) / 60), 1) AS avg_delay_min
FROM flights f
JOIN airports ap ON ap.airport_code = f.departure_airport
WHERE f.status = 'Arrived'
GROUP BY ap.city
HAVING COUNT(*) >= 300
ORDER BY avg_delay_min DESC;
| city | flights | avg_delay_min |
|---|---|---|
| Yekaterinburg | 348 | 14.7 |
| Bryansk | 306 | 13.2 |
| Krasnoyarsk | 358 | 13.1 |
| St. Petersburg | 956 | 12.6 |
| Moscow | 3990 | 12.4 |
| Novosibirsk | 536 | 11.8 |
| Perm | 311 | 10.5 |
| Rostov-on-Don | 310 | 9.2 |
A subtração de duas datas dá um intervalo; EXTRACT(EPOCH FROM …) converte esse intervalo em segundos, e dividimos por 60.
Em que ordem uma consulta é executada
A gente escreve o SELECT primeiro, mas o banco de dados o processa quase no fim:
FROMeJOIN: monta a tabela de trabalho.WHERE: descarta linhas.GROUP BY: forma os grupos.HAVING: descarta grupos.SELECT: calcula as colunas e os aliases.ORDER BY: ordena (aqui você já pode usar os aliases do SELECT).LIMIT: corta.
Essa ordem explica por que um alias do SELECT funciona no ORDER BY, mas não no WHERE, e por que as agregações vão no HAVING.
Erros comuns de iniciante
= NULLem vez deIS NULL. Devolve zero linhas sem nenhum aviso.- Aspas duplas para texto.
WHERE city = "Moscow"no PostgreSQL procura uma coluna chamada Moscow. - Colunas não agrupadas no SELECT. Tudo o que não estiver no
GROUP BYprecisa ir dentro de uma agregação. LIMITsemORDER BY. O resultado não é “o top”, é aleatório.COUNT(*)depois de umLEFT JOIN. Conta como 1 as linhas sem par.- Datas com
BETWEENem colunas com hora. Use>=início e<dia seguinte.
Como praticar
Ler consultas ajuda, mas se aprende escrevendo e errando. Todos os exemplos deste guia podem ser executados e modificados na sala de SQL da Nomto, com o mesmo banco de dados: troque a cidade, o status ou a ordenação e veja o que acontece. O primeiro módulo do curso começa justamente por SELECT e aliases, e cada aula termina com três exercícios corrigidos automaticamente. Quando dominar o básico, siga para os exercícios de SQL com respostas e para as funções de janela.
Perguntas frequentes
Quanto tempo leva para aprender os comandos SQL básicos?
SELECT, WHERE, ORDER BY, GROUP BY e JOIN podem ser aprendidos em duas ou três semanas praticando uma hora por dia. O que leva mais tempo é ler bem os dados: saber o que é uma linha e quando um JOIN multiplica linhas.
Qual a diferença entre WHERE e HAVING?
O WHERE filtra linhas antes de agrupar e não aceita agregações. O HAVING filtra os grupos já formados e é usado com COUNT, SUM, AVG e companhia.
O SQL deste guia serve para MySQL ou SQL Server?
Quase tudo, sim. SELECT, WHERE, GROUP BY, HAVING, JOIN e ORDER BY são iguais em todos os sistemas. Mudam detalhes: no SQL Server, LIMIT vira TOP; ILIKE não existe no MySQL nem no SQL Server; e as funções de data têm nomes diferentes.
Preciso instalar um banco de dados para praticar?
Não. Na sala de SQL da Nomto você escreve a consulta no navegador e a executa no banco de dados da companhia aérea, sem instalar nada.
Outros idiomas: English, Tiếng Việt, 简体中文, Español