SELECT: extraindo os dados certos

Desafio final: SELECT em ação

22 min
O que você vai aprender
  • consolidar a habilidade de montar um SELECT completo: escolher colunas, dar apelidos a elas, filtrar linhas, ordenar o resultado e limitar a saída
  • revisar os principais filtros do módulo: WHERE, BETWEEN, IN, LIKE, IS NULL, IS NOT NULL
  • consolidar a dupla ORDER BY ... LIMIT ... para tops e relatórios curtos
  • lembrar quando o DISTINCT é necessário e quando é melhor pensar em agrupamento
  • revisar o CASE como jeito de acrescentar rótulos compreensíveis à saída
  • aprender a ler com mais atenção consultas que são sintaticamente corretas, mas que logicamente devolvem o resultado errado
  • consolidar os hábitos de uma consulta confiável: parênteses em condições mistas, ELSE explícito, ordenação explícita e cuidado ao lidar com NULL

Vamos consolidar o SELECT em tarefas reais

A QUERY apaga os painéis extras. No salão ficam um terminal e três tarefas: o exame do primeiro acesso.

A Academia não acredita em discurso — só em consultas.

Ao longo deste módulo você montou o kit básico do arquivista-leitor. Agora você não escreve apenas:

SELECT *
FROM products;

mas faz ao banco uma pergunta precisa.

Você escolhe as colunas que interessam:

SELECT name, price
FROM products;

Dá a elas nomes compreensíveis:

SELECT
    name AS product_name,
    price AS product_price
FROM products;

Deixa apenas as linhas necessárias:

SELECT name, price
FROM products
WHERE price >= 1000;

Lida com os valores ausentes:

SELECT id, name, city
FROM users
WHERE city IS NULL;

Escreve filtros curtos com faixas, listas e padrões:

SELECT name, category, price
FROM products
WHERE category IN ('Книги', 'Игрушки')
  AND price BETWEEN 1000 AND 3000
  AND name LIKE 'К%';

Define a ordem:

SELECT name, price
FROM products
ORDER BY price DESC;

Pega o topo do resultado:

SELECT name, price
FROM products
ORDER BY price DESC
LIMIT 5;

Remove as repetições quando precisa de um dicionário de valores:

SELECT DISTINCT status
FROM orders;

E acrescenta uma lógica simples direto na saída:

SELECT
    name,
    price,
    CASE
      WHEN price < 1500 THEN 'barato'
      WHEN price < 4000 THEN 'médio'
      ELSE 'caro'
    END AS segment
FROM products;

Agora é hora de juntar tudo isso na cabeça como uma ferramenta só.

Não como comandos separados.
Não como um punhado de operadores soltos.
E sim como o caminho completo de uma consulta: da tabela até um resultado que qualquer pessoa entende.

QUERY: O primeiro acesso não se conquista por saber as palavras. Conquista-se por fazer ao arquivo a pergunta exata.

Um salão na penumbra, um terminal com três tarefas e um bloco de dados criptografado e trancado, cintilando no fundo do holograma
O exame do primeiro acesso: três consultas entre você e o próximo nível do arquivo.

Como ler um SELECT completo

Quando a consulta passa de três linhas, quem está começando costuma se perder. Parece que o SQL é executado rigorosamente de cima para baixo, como um texto.

Mas é mais fácil ler a consulta por camadas de sentido.

Por exemplo:

SELECT
    name,
    category,
    price,
    CASE
      WHEN price < 1500 THEN 'barato'
      WHEN price < 4000 THEN 'médio'
      ELSE 'caro'
    END AS segment
FROM products
WHERE category IN ('Книги', 'Игрушки')
  AND price BETWEEN 1000 AND 3000
ORDER BY price DESC, id
LIMIT 5;

Uma consulta assim pode ser destrinchada passo a passo.

Primeiro, a fonte:

FROM products

O banco pega a tabela de produtos.

Depois, o filtro das linhas:

WHERE category IN ('Книги', 'Игрушки')
  AND price BETWEEN 1000 AND 3000

Sobram apenas livros e brinquedos com preço de 1000 a 3000, inclusive.

Depois, a lista de colunas e de cálculos:

SELECT
    name,
    category,
    price,
    CASE ... END AS segment

A consulta mostra o nome, a categoria e o preço e acrescenta o segmento com um CASE.

Depois, a ordem:

ORDER BY price DESC, id

Primeiro os itens mais caros e, quando o preço empata, uma ordenação estável por id.

E só então o limite:

LIMIT 5

Ficam as cinco primeiras linhas, já depois da ordenação.

A ideia central:

O LIMIT não escolhe sozinho as “melhores” linhas. Ele apenas pega o topo da ordem que você definiu no ORDER BY.

O que torna uma consulta confiável

Neste nível, o importante não é só ganhar o certinho verde. É começar a pensar como alguém que entende por que a consulta retornou exatamente aquelas linhas.

Uma consulta confiável costuma ter três características.

A primeira: ela diz de forma explícita quais linhas quer.

É ruim quando a condição precisa ser adivinhada a partir de um pedaço da lógica. É bom quando o WHERE se lê como uma regra precisa:

WHERE category IN ('Книги', 'Игрушки')
  AND price BETWEEN 1000 AND 3000

A segunda: ela diz de forma explícita em que ordem o resultado deve vir.

Se o que importa são os itens mais caros, os pedidos mais recentes ou os primeiros usuários em ordem alfabética, isso precisa estar escrito no ORDER BY:

ORDER BY price DESC, id

A terceira: ela não esconde a incerteza.

Se os dados podem conter NULL, a consulta deve levar isso em conta explicitamente:

WHERE city IS NULL

ou:

WHERE city <> 'Москва'
   OR city IS NULL

Se no CASE existem linhas que não caem em nenhum ramo, é melhor decidir de antemão o que fazer com elas:

ELSE 'outro status'

Se a ordem entre valores iguais importa, complete-a com uma chave única:

ORDER BY price DESC, id

Em tabelas pequenas de estudo, muitas falhas passam despercebidas. Mas, num banco real, esses detalhes viram erros silenciosos: a consulta roda sem quebrar, só que o resultado não é o esperado.

Erros silenciosos: quando a consulta funciona, mas mente

Os erros mais desagradáveis do SQL nem sempre aparecem como um erro de sintaxe em vermelho.

Bem mais comum é a consulta rodar, retornar uma tabela — e as linhas não serem as certas.

Por exemplo, a comparação com NULL:

WHERE city = NULL

A consulta pode estar sintaticamente correta, mas não vai encontrar as linhas sem cidade. Para NULL, é preciso uma verificação própria:

WHERE city IS NULL

Ou uma condição que mistura AND e OR:

WHERE category = 'Книги'
   OR category = 'Игрушки'
  AND price < 3000

Batendo o olho, a consulta se lê como:

livros ou brinquedos, e preço menor que 3000

Mas o SQL agrupa primeiro o AND e só depois o OR. Por isso, a lógica de verdade é outra:

WHERE category = 'Книги'
   OR (category = 'Игрушки' AND price < 3000)

Se você quer aplicar o preço às duas categorias, precisa de parênteses:

WHERE (category = 'Книги' OR category = 'Игрушки')
  AND price < 3000

Ou assim, ainda mais limpo:

WHERE category IN ('Книги', 'Игрушки')
  AND price < 3000

Outra falha silenciosa é um CASE sem ELSE:

CASE
  WHEN status = 'paid' THEN 'pago'
  WHEN status = 'pending' THEN 'aguardando'
END AS status_label

Se aparecer o status cancelled, o rótulo vira NULL. Às vezes isso é aceitável, mas, num relatório, costuma ser melhor escrever um plano B explícito:

ELSE 'outro status'

E, por fim, o LIMIT sem ORDER BY:

SELECT name, price
FROM products
LIMIT 5;

Essa consulta não seleciona o top 5. Ela pega cinco linhas quaisquer, na ordem que o banco resolveu retornar desta vez.

Para um top, é preciso um critério:

SELECT name, price
FROM products
ORDER BY price DESC, id
LIMIT 5;

Os erros silenciosos são perigosos justamente porque o banco não discute. Ele executa o que está escrito. Mesmo que você quisesse dizer outra coisa.

QUERY: O arquivo não lê intenções. Ele lê a consulta. Se o pensamento não estiver escrito de forma explícita, ele não existe.

Antes de enviar a consulta

Antes de mandar a solução no simulador, vale passar os olhos pela consulta com calma.

Primeiro, confira se você escolheu exatamente as colunas pedidas.

Se a tarefa pede:

name, price

não deixe:

SELECT *

Mesmo que seja mais prático durante o rascunho, a resposta final deve mostrar o que a tarefa pediu.

Depois, confira o filtro.

Se o enunciado diz “de 1000 a 3000, inclusive”, cai bem:

BETWEEN 1000 AND 3000

Se ele diz “categoria Livros ou Brinquedos”, fica mais legível usar:

IN ('Книги', 'Игрушки')

Se precisar de “começa com”, serve:

LIKE 'К%'

Se precisar de “contém”, serve:

LIKE '%К%'

Mas lembre-se: um % no começo pode sair caro em tabelas grandes.

Depois, confira o NULL.

Se precisar encontrar um valor ausente, não escreva:

= NULL

Escreva:

IS NULL

Se precisar excluir um valor, mas manter as linhas com NULL, diga isso explicitamente:

WHERE city <> 'Москва'
   OR city IS NULL

Depois, confira a ordenação.

Se a tarefa traz palavras como:

mais caros
mais baratos
últimos
primeiros
top

você quase com certeza vai precisar de um ORDER BY.

Se houver um limite na quantidade de linhas, quase com certeza vai precisar de um LIMIT.

E, por fim, se você ordena por uma coluna em que podem existir valores iguais, acrescente uma chave única:

ORDER BY price DESC, id

Assim o resultado fica estável.

O que você já sabe fazer ao fim do módulo

Este módulo foi sobre ler linhas.

Você ainda não calcula receita, ticket médio nem quantidade de pedidos por grupo — isso é o próximo nível. Mas já sabe tirar da tabela exatamente a fatia de dados que a resposta pede.

Você tem à mão o percurso básico:

SELECT ...
FROM ...
WHERE ...
ORDER BY ...
LIMIT ...

Você entende para que servem os apelidos:

price * stock AS stock_value

Você sabe lidar com valores ausentes:

IS NULL
IS NOT NULL
COALESCE(...)

Você sabe escrever filtros curtos:

BETWEEN
IN
LIKE

Você sabe obter um dicionário de valores:

SELECT DISTINCT status
FROM orders;

Você sabe acrescentar rótulos legíveis ao resultado:

CASE
  WHEN ... THEN ...
  ELSE ...
END

E o mais importante: você já sabe que uma sintaxe correta não garante um pensamento correto.

O SQL pode executar a consulta com perfeição e ainda assim retornar algo diferente do que você queria, se a condição estiver escrita de forma imprecisa.

Interview question

Pergunta de entrevista:
A consulta é sintaticamente correta, mas as linhas somem da saída sem aviso ou recebem valores errados. Cite as causas típicas.

Resposta forte:
Uma causa frequente é lidar mal com NULL. Comparações com =, <> e algumas variantes de NOT IN, quando um NULL aparece no meio, podem dar UNKNOWN. E o WHERE só deixa passar TRUE, então as linhas desaparecem sem nenhum erro. Para NULL são necessários IS NULL, IS NOT NULL, às vezes COALESCE ou IS DISTINCT FROM no PostgreSQL.

A segunda causa é misturar AND e OR sem parênteses. O AND tem prioridade mais alta, então a condição pode se agrupar de um jeito diferente daquele que a pessoa leu. Se a lógica não for óbvia, é melhor colocar os parênteses explicitamente.

A terceira causa é um CASE incompleto. Se nenhum WHEN disparar e não houver um ELSE escrito, o resultado será NULL. E, na forma simples, o ramo CASE x WHEN NULL THEN ... não dispara, porque ali há uma comparação por x = NULL.

A quarta causa é LIMIT sem ORDER BY. Uma consulta dessas não devolve as «primeiras linhas» em algum sentido: devolve só uma certa quantidade de linhas em ordem indefinida. Para tops e páginas é preciso um ORDER BY explícito e, para haver estabilidade quando os valores empatam, uma ordenação adicional por uma chave única.

Numa entrevista, o importante não é apenas listar esses casos, mas mostrar o hábito de escrever de forma confiável: verificar NULL explicitamente, colocar parênteses em condições mistas, acrescentar um ELSE ao CASE e não usar o LIMIT como substituto da ordenação.

QUERY: Daqui em diante, o arquivo só se abre para quem sabe contar. Receita, ticket médio, grupos — o próximo nível de acesso.