Pesquisar conteúdos

Encontre uma trilha ou aula pelo assunto.

Experimente pesquisar por “governança”, “ITIL” ou “planejamento”.

SELECT, filtros, ordenação, aliases e valores NULL

Construa consultas legíveis para selecionar colunas, filtrar linhas, ordenar resultados e tratar corretamente valores ausentes.
Uma relação passa pela seleção de colunas, filtro de linhas, aliases, ordenação e tratamento explícito de valores desconhecidos

SELECT transforma uma fonte em uma relação de resultado

Na aula anterior, vimos que SQL define estruturas, opera dados, controla privilégios e delimita transações. Agora aprofundaremos a consulta mais frequente: SELECT.

Uma consulta declara quais colunas produzir, de qual fonte obter linhas, quais linhas preservar e em qual ordem apresentar o resultado. Ela não é apenas uma leitura da tabela inteira: pode projetar colunas, calcular expressões, renomear saídas, filtrar valores e ordenar o conjunto final.

SELECT
  trilha_id,
  titulo,
  publicada_em
FROM trilha
WHERE situacao = 'publicada'
ORDER BY publicada_em DESC, trilha_id ASC;

Neste exemplo, FROM fornece as linhas candidatas, WHERE mantém apenas as publicadas, SELECT determina as colunas de saída e ORDER BY estabelece uma ordem previsível.

A ordem escrita não é a ordem lógica

Uma forma útil de entender a consulta básica é separar a sintaxe escrita da transformação lógica:

Escrita: SELECT → FROM → WHERE → ORDER BY → LIMIT
Lógica:  FROM → WHERE → SELECT → DISTINCT → ORDER BY → LIMIT

Essa sequência explica comportamentos que parecem estranhos no início. Um alias criado na lista de saída pode ser usado em ORDER BY, que atua depois dela, mas normalmente não pode ser usado em WHERE, que filtra antes da lista de saída existir.

SELECT duracao_minutos / 60.0 AS duracao_horas
FROM trilha
WHERE duracao_minutos >= 60
ORDER BY duracao_horas DESC;

Escrever WHERE duracao_horas >= 1 nessa mesma camada não funciona no PostgreSQL: o alias ainda não faz parte da tabela virtual filtrada. Repita a expressão ou mova o cálculo para uma subconsulta/CTE quando essa abstração realmente melhorar a leitura.

Fluxo lógico de uma consulta: FROM fornece linhas, WHERE mantém apenas condições verdadeiras, SELECT projeta e nomeia colunas, DISTINCT remove duplicatas, ORDER BY organiza e LIMIT recorta
A ordem lógica ajuda a prever quando linhas desaparecem e em quais cláusulas um nome de saída já está disponível.

Essa é uma explicação conceitual. O otimizador pode reorganizar operações físicas quando provar que o resultado continua equivalente.

SELECT define as colunas de saída

Prefira colunas explícitas

SELECT trilha_id, titulo, situacao
FROM trilha;

SELECT * solicita todas as colunas produzidas por FROM. É útil na exploração pontual, mas costuma ser uma interface frágil em aplicações:

  • uma alteração no esquema muda silenciosamente a saída;
  • colunas grandes ou sensíveis podem ser transportadas sem necessidade;
  • duas consultas visualmente iguais podem passar a retornar formatos diferentes;
  • o contrato fica menos claro para quem revisa o código.

Selecionar explicitamente não é uma regra absoluta contra *; é uma forma de declarar o contrato quando a saída será consumida por outro componente.

A lista aceita expressões

Cada item pode ser uma coluna, literal, operação ou chamada de função:

SELECT
  titulo,
  duracao_minutos,
  ROUND(duracao_minutos / 60.0, 2) AS duracao_horas,
  situacao = 'publicada' AS esta_publicada
FROM trilha;

A expressão é avaliada para cada linha que chegou à lista de seleção. Ela cria uma coluna no resultado; não altera a coluna armazenada.

Alias nomeia a saída, não o dado original

SELECT
  titulo AS nome_da_trilha,
  publicada_em AS data_de_publicacao
FROM trilha AS t;

Há dois aliases diferentes:

  • nome_da_trilha e data_de_publicacao são rótulos das colunas de saída;
  • t é o nome temporário da fonte durante a consulta.

O alias não renomeia a tabela ou coluna no esquema. AS é opcional em várias posições, mas mantê-lo para colunas torna a intenção visível. Para tabelas, aliases curtos ajudam principalmente quando há várias fontes; a aula de JOINs aprofundará esse uso.

Evite aliases como x, valor2 ou campo_final quando a saída possui significado estável. O nome é parte do contrato entregue ao cliente.

WHERE filtra por um predicado

WHERE recebe uma expressão booleana para cada linha. Apenas linhas cujo resultado seja TRUE continuam.

Comparações básicas

SELECT trilha_id, titulo, duracao_minutos
FROM trilha
WHERE duracao_minutos >= 120;

Operadores frequentes incluem:

OperadorSignificado
=igual
<>diferente no padrão SQL
<, <=menor; menor ou igual
>, >=maior; maior ou igual

O PostgreSQL também aceita != para diferente, mas <> é a grafia do padrão SQL.

Compare tipos compatíveis. Uma data deve ser tratada como data, não como texto dependente de formato:

SELECT titulo, publicada_em
FROM trilha
WHERE publicada_em >= DATE '2026-01-01';

BETWEEN inclui as extremidades

SELECT titulo, duracao_minutos
FROM trilha
WHERE duracao_minutos BETWEEN 60 AND 180;

Esse predicado equivale a:

duracao_minutos >= 60 AND duracao_minutos <= 180

As duas extremidades estão incluídas. Para timestamps, um intervalo semiaberto costuma ser mais seguro do que tentar representar “o último instante do dia”:

WHERE publicada_em >= TIMESTAMP '2026-08-01 00:00:00'
  AND publicada_em <  TIMESTAMP '2026-09-01 00:00:00'

IN compara com um conjunto explícito

SELECT titulo, situacao
FROM trilha
WHERE situacao IN ('publicada', 'arquivada');

Isso expressa várias igualdades ligadas por OR. Listas grandes, dinâmicas ou originadas de outra relação podem pedir outra estrutura; não concatene entradas externas para montar o conteúdo de IN.

LIKE procura padrões textuais

SELECT titulo
FROM trilha
WHERE titulo LIKE 'Banco%';

No padrão de LIKE:

  • % representa qualquer sequência de caracteres;
  • _ representa exatamente um caractere.

No PostgreSQL, ILIKE faz correspondência sem diferenciar maiúsculas de minúsculas segundo o locale, mas é uma extensão do produto. Regras linguísticas, collation e indexação afetam busca textual; não presuma que converter tudo com LOWER resolve qualquer idioma ou desempenho.

AND, OR e NOT compõem condições

SELECT titulo, situacao, duracao_minutos
FROM trilha
WHERE situacao = 'publicada'
  AND duracao_minutos >= 90;
SELECT titulo, situacao
FROM trilha
WHERE situacao = 'publicada'
   OR situacao = 'arquivada';

NOT nega uma condição:

WHERE NOT situacao = 'arquivada'

Por precedência, NOT é avaliado antes de AND, e AND antes de OR. Mesmo conhecendo a regra, use parênteses quando houver mais de uma interpretação plausível:

WHERE situacao = 'publicada'
  AND (nivel = 'basico' OR nivel IS NULL)

Sem os parênteses, uma leitura rápida poderia atribuir o filtro de situação apenas ao primeiro nível. Clareza é parte da correção.

NULL representa ausência ou desconhecimento

NULL não é zero, string vazia, falso ou uma data especial. Ele indica que não há um valor conhecido naquela posição, conforme a semântica definida para a coluna.

Na plataforma:

  • publicada_em IS NULL pode significar que uma trilha ainda não foi publicada;
  • concluida_em IS NULL pode significar que uma matrícula ainda está em andamento;
  • um dado opcional pode permanecer desconhecido.

Esses significados são diferentes. O dicionário de dados precisa explicá-los; NULL sozinho não diz por que o valor está ausente.

Não compare NULL com igualdade

-- Incorreto para testar ausência
WHERE publicada_em = NULL

Uma comparação comum com NULL produz UNKNOWN, não TRUE:

WHERE publicada_em IS NULL
WHERE publicada_em IS NOT NULL

SQL possui lógica de três valores

Além de TRUE e FALSE, uma expressão pode resultar em UNKNOWN, representado por NULL booleano. WHERE preserva somente TRUE; tanto FALSE quanto UNKNOWN são descartados.

Considere três trilhas com nivel: 'basico', 'avancado' e NULL.

SELECT titulo, nivel
FROM trilha
WHERE nivel <> 'avancado';

A linha com 'basico' permanece. A linha com 'avancado' produz FALSE. A linha com NULL produz UNKNOWN e também sai. Se a regra deseja “qualquer nível diferente de avançado, incluindo desconhecido”, declare isso:

WHERE nivel <> 'avancado'
   OR nivel IS NULL

Ou, quando disponível e semanticamente adequado:

WHERE nivel IS DISTINCT FROM 'avancado'

IS DISTINCT FROM trata NULL como valor comparável para esse teste e sempre retorna verdadeiro ou falso. IS NOT DISTINCT FROM funciona como igualdade que considera dois nulos equivalentes.

Três linhas avaliadas por WHERE resultam em verdadeiro, falso e desconhecido; somente verdadeiro atravessa o filtro, enquanto IS NULL e IS DISTINCT FROM tratam explicitamente a ausência
`WHERE` não pergunta apenas se a condição é falsa: ele mantém exclusivamente `TRUE`. O estado `UNKNOWN` também fica fora.

AND, OR e NOT propagam UNKNOWN

Alguns casos essenciais:

ABA AND BA OR B
TRUEUNKNOWNUNKNOWNTRUE
FALSEUNKNOWNFALSEUNKNOWN
UNKNOWNUNKNOWNUNKNOWNUNKNOWN

NOT UNKNOWN continua UNKNOWN. O valor conhecido pode decidir a expressão: FALSE AND UNKNOWN já é falso; TRUE OR UNKNOWN já é verdadeiro.

NOT IN pode surpreender com NULL

WHERE situacao NOT IN ('arquivada', NULL)

Para uma situação diferente de 'arquivada', a comparação com o primeiro item é verdadeira para “diferente”, mas a comparação com NULL é desconhecida. A conjunção não chega a TRUE, e a linha é descartada. Evite nulos na lista e expresse a intenção positiva quando possível.

COALESCE substitui, mas também escolhe significado

COALESCE retorna o primeiro argumento não nulo:

SELECT
  titulo,
  COALESCE(nivel, 'nao informado') AS nivel_exibicao
FROM trilha;

Isso pode ser adequado na apresentação. Porém, substituir ausência por zero, vazio ou uma categoria real pode misturar estados distintos:

COALESCE(duracao_minutos, 0)

Agora “duração desconhecida” parece “duração zero”. Use a substituição somente quando os dois estados forem equivalentes para aquela operação.

DISTINCT elimina duplicatas da saída

SELECT DISTINCT situacao
FROM trilha;

DISTINCT considera a combinação completa das expressões selecionadas. Se a lista inclui situacao e nivel, duas linhas são duplicadas apenas quando esses dois valores coincidem segundo as regras aplicáveis.

SELECT DISTINCT situacao, nivel
FROM trilha;

Para eliminação de duplicatas, valores nulos na mesma posição são tratados como iguais no PostgreSQL. Isso não contradiz a lógica de NULL = NULL, que continua desconhecida: são operações com finalidades e regras diferentes.

ORDER BY é a única garantia de ordem

Sem ORDER BY, a ordem das linhas é não especificada. Ela pode mudar com volume, índices, paralelismo, estatísticas, versão ou plano de execução.

SELECT trilha_id, titulo, publicada_em
FROM trilha
ORDER BY publicada_em DESC;

ASC é ascendente; DESC é descendente. Para empates, acrescente critérios:

SELECT trilha_id, titulo, publicada_em
FROM trilha
ORDER BY
  publicada_em DESC,
  titulo ASC,
  trilha_id ASC;

Cada chave posterior só decide entre linhas empatadas nas anteriores. O identificador ao final cria uma ordem total quando é único.

Controle a posição de NULL

No PostgreSQL:

ORDER BY publicada_em DESC NULLS LAST, trilha_id ASC

NULLS FIRST e NULLS LAST deixam a intenção explícita. O padrão inicial de posicionamento pode variar por direção e por SGBD; declarar evita depender de uma convenção implícita.

Alias de saída pode ordenar

SELECT
  titulo,
  duracao_minutos / 60.0 AS duracao_horas
FROM trilha
ORDER BY duracao_horas DESC, titulo ASC;

No PostgreSQL, o alias pode aparecer sozinho em ORDER BY. Evite criar um alias igual ao nome de outra coluna disponível, pois a resolução pode ficar difícil de entender.

Ordenar por posição, como ORDER BY 2, também é aceito em diversos produtos, mas fica frágil quando a lista de saída muda. Prefira nomes ou expressões claros.

LIMIT recorta depois da ordenação

SELECT trilha_id, titulo, publicada_em
FROM trilha
WHERE situacao = 'publicada'
ORDER BY publicada_em DESC, trilha_id DESC
LIMIT 10;

Sem ORDER BY, “as primeiras dez” não possui significado estável. Mesmo com uma coluna ordenada, empates podem trocar de posição; inclua uma chave única como desempate.

SELECT trilha_id, titulo, publicada_em
FROM trilha
WHERE situacao = 'publicada'
ORDER BY publicada_em DESC, trilha_id DESC
LIMIT 10 OFFSET 20;

OFFSET ignora linhas antes do recorte, mas o servidor ainda pode precisar calculá-las. Em páginas profundas, isso pode custar mais e sofrer deslocamento se linhas forem inseridas ou removidas entre requisições. Paginação por cursor — usando os últimos valores ordenados — costuma ser mais estável em fluxos dinâmicos, desde que preserve exatamente a mesma ordem total.

Linhas são ordenadas por data e identificador; empates de data recebem desempate único, valores nulos vão ao fim e um recorte de dez linhas forma uma página estável
Ordenação previsível exige critérios suficientes para distinguir todas as linhas. Só depois faz sentido aplicar `LIMIT` ou construir um cursor.

Um exemplo completo

Requisito: listar até dez trilhas publicadas desde 2026, com duração conhecida entre uma e cinco horas, priorizando as publicações mais recentes. Empates devem ser estáveis, e a saída precisa ter nomes adequados à API.

SELECT
  t.trilha_id AS id,
  t.titulo,
  ROUND(t.duracao_minutos / 60.0, 2) AS duracao_horas,
  t.publicada_em AS publicada_em
FROM trilha AS t
WHERE t.situacao = 'publicada'
  AND t.publicada_em >= DATE '2026-01-01'
  AND t.duracao_minutos BETWEEN 60 AND 300
ORDER BY
  t.publicada_em DESC,
  t.trilha_id DESC
LIMIT 10;

Leia a consulta por intenção:

  1. FROM escolhe a fonte e cria o alias t;
  2. WHERE mantém somente linhas que cumprem todos os requisitos;
  3. SELECT define contrato e cálculo da saída;
  4. ORDER BY cria prioridade e desempate;
  5. LIMIT recorta o resultado já ordenado.

A condição de duração exclui NULL porque NULL BETWEEN 60 AND 300 resulta em UNKNOWN. Isso corresponde ao requisito “duração conhecida”. Se desconhecidas devessem aparecer, o predicado precisaria declarar essa opção.

Parametrize valores da aplicação

Valores fornecidos por uma interface devem seguir separados do texto SQL:

SELECT trilha_id, titulo
FROM trilha
WHERE situacao = $1
  AND publicada_em >= $2
ORDER BY publicada_em DESC, trilha_id DESC
LIMIT $3;

O formato $1, $2, $3 é comum em clientes PostgreSQL; outros drivers usam marcadores diferentes. Não concatene situação, data ou limite diretamente na instrução.

Parâmetros não substituem livremente identificadores, operadores ou ASC/DESC. Para ordenação escolhida pelo usuário, a aplicação deve mapear uma opção permitida para um trecho conhecido, em vez de aceitar sintaxe arbitrária.

Erros comuns

  • usar SELECT * como contrato permanente: mudanças no esquema alteram a saída;
  • imaginar que a ordem da tabela será preservada: somente ORDER BY garante apresentação;
  • ordenar sem desempate único: empates deixam páginas instáveis;
  • usar alias de saída em WHERE: essa etapa lógica ocorre antes da projeção;
  • escrever = NULL ou <> NULL: use IS NULL, IS NOT NULL ou predicados distintos quando apropriado;
  • tratar UNKNOWN como FALSE em toda lógica: ambos saem de WHERE, mas combinam de maneiras diferentes com AND, OR e NOT;
  • usar NOT IN com lista que pode conter nulo: o resultado pode nunca ser verdadeiro;
  • aplicar COALESCE sem pensar no significado: ausência pode virar falsamente zero ou vazio;
  • adicionar DISTINCT para esconder duplicação incompreendida: investigue a granularidade;
  • usar LIMIT sem ordem total: o subconjunto pode variar;
  • concatenar filtros e ordenações vindos da interface: parametrize valores e permita apenas estruturas conhecidas;
  • confundir ordem lógica com plano físico: o SGBD pode otimizar sem mudar a semântica.

Checklist de uma consulta previsível

  1. Qual é a granularidade de cada linha do resultado?
  2. Quais colunas realmente fazem parte do contrato?
  3. Cada filtro inclui e exclui exatamente os estados desejados?
  4. Como NULL afeta cada comparação?
  5. Os parênteses deixam AND e OR inequívocos?
  6. DISTINCT é requisito real ou máscara para outra falha?
  7. A ordem foi declarada?
  8. Existe um desempate único?
  9. NULLS FIRST ou NULLS LAST precisa ser explícito?
  10. LIMIT e paginação usam essa mesma ordem total?
  11. Entradas externas estão parametrizadas ou mapeadas por lista permitida?
  12. Nomes de saída comunicam significado ao consumidor?

O que você deve guardar

SELECT cria uma relação de resultado. FROM fornece as linhas; WHERE mantém apenas predicados verdadeiros; a lista de seleção projeta e nomeia colunas; DISTINCT elimina duplicatas da saída; ORDER BY estabelece a única ordem garantida; LIMIT recorta o conjunto depois dessa ordenação.

NULL representa ausência ou desconhecimento e introduz um terceiro resultado lógico. Comparações comuns com nulo não são verdadeiras nem falsas: são desconhecidas. Consultas corretas expressam explicitamente ausência, desempates e estados opcionais.

Na próxima aula, aprenderemos como JOINs combinam relações e quais linhas são preservadas por INNER, LEFT, RIGHT, FULL e CROSS JOIN.

Referências