QA Learning / PostgreSQL

Módulo 2: Tipos de verdade

boolean, numeric x divisão inteira, CAST/::, NULL, IS DISTINCT FROM

Antes de começar

O SQLite é frouxo com tipos: aceita ativo = 1, junta texto com número sem reclamar. O Postgres é rígido, e isso é bom: erro de tipo vira erro na hora, não dado torto que só aparece semanas depois.

  • boolean: ativo aqui é true/false, não 0/1. WHERE ativo já basta (é "onde ativo é verdadeiro"). O contrário é WHERE NOT ativo. Também existe ativo IS TRUE / IS FALSE / IS NOT TRUE (esse último pega inclusive NULL).
  • Divisão inteira. 5 / 2 dá 2 (dois inteiros → resultado inteiro). Pra ter 2.5: 5 / 2.0 ou 5::numeric / 2. Pega muita gente em cálculo de taxa/percentual.
  • CAST(x AS tipo) ou x::tipo: converte de propósito: '42'::int, preco::int, data_abertura::text.
  • NULL não é igual a nada, nem a outro NULL. Teste com IS NULL / IS NOT NULL. COALESCE(a, b) devolve o primeiro não-nulo. a IS DISTINCT FROM b compara tratando NULL como um valor comum (útil pra "mudou?").

Olhar de QA: "esse campo devia ser numérico e veio texto" ou "somaram 0/1 num campo boolean" são bugs clássicos: no Postgres o próprio banco te avisa.

Dois exemplos

Teoria

  • WHERE ativo: coluna boolean já é a condição. Negativo: WHERE NOT ativo.
  • coluna IS TRUE / IS FALSE / IS NOT TRUE: o IS NOT TRUE também casa com NULL.
  • 5 / 2 = 2 (inteiro). 5::numeric / 2 = 2.5. ROUND(x::numeric, 2) arredonda.
  • x::tipo = CAST(x AS tipo). Ex.: valor_total::int, '2025-01-01'::date.
  • COALESCE(a, b, …): primeiro valor não-nulo. NULLIF(a, b): vira NULL se a = b.
  • a IS DISTINCT FROM b: "são diferentes", contando NULL como valor.

Exemplos de aplicação

Um exemplo montado pra cada item da teoria acima. Não precisa rodar, é pra ver a cara do recurso e entender cada pedaço. Os valores são ilustrativos.

Coluna boolean como condição

SELECT nome, ativo
FROM produtos
WHERE ativo;          -- só os à venda

-- inativos:
-- WHERE NOT ativo;

ativo já é true/false, então WHERE ativo basta, não precisa de = 1 nem = TRUE. Pra negar, WHERE NOT ativo. No SQLite isso era ativo = 1; no Postgres é boolean de verdade.

IS TRUE / IS FALSE / IS NOT TRUE

SELECT nome, ativo
FROM produtos
WHERE ativo IS NOT TRUE;

IS NOT TRUE pega tanto false quanto NULL, diferente de WHERE NOT ativo, que deixa o NULL de fora. Use quando "não é verdade" precisa incluir os sem valor.

Divisão inteira vs decimal

SELECT
  5 / 2                     AS inteiro,      -- 2
  5::numeric / 2            AS decimal,      -- 2.5000000000000000
  ROUND(5::numeric / 2, 2)  AS arredondado  -- 2.50
FROM produtos
LIMIT 1;

Dois inteiros divididos dão inteiro (o Postgres corta a parte fracionária). Convertendo um lado pra numeric, a divisão passa a ter casas. ROUND(x, 2) ajusta o número de casas.

x::tipo (cast)

SELECT
  valor_total,
  valor_total::int    AS sem_centavos,
  '2025-01-01'::date  AS virou_data
FROM pedidos
LIMIT 5;

x::tipo é a forma curta de CAST(x AS tipo): converte o valor. valor_total::int descarta os centavos; '2025-01-01'::date transforma o texto em data de verdade (aí dá pra fazer conta com ela).

COALESCE / NULLIF

SELECT
  id,
  COALESCE(data_fechamento::text, 'aberto') AS situacao,
  NULLIF(severidade, 'baixa')               AS severidade_relevante
FROM defeitos;

COALESCE(a, b) devolve o primeiro não-nulo. NULLIF(a, b) faz o contrário: devolve NULL quando a = b (aqui, some com o 'baixa'), senão devolve a.

a IS DISTINCT FROM b

SELECT id, status, reportado_por
FROM defeitos
WHERE reportado_por IS DISTINCT FROM 'ezequiel';

É o "<> que trata NULL como valor". reportado_por <> 'ezequiel' deixa linhas com NULL de fora; IS DISTINCT FROM inclui essas linhas (NULL é "distinto de" qualquer texto).

Pegadinhas deste módulo

Os erros que quase todo mundo comete aqui. Ler agora poupa meia hora de confusão depois, e alguns deles são a causa de relatório errado em projeto real.

  • NULL não é igual a nada, nem a outro NULL. WHERE data_fechamento = NULL devolve zero linhas sempre, mesmo havendo milhares com o campo vazio. O certo é IS NULL. Não dá erro: dá resultado vazio, que é pior.
  • <> descarta as linhas com NULL em silêncio. WHERE status <> 'fechado' não traz as linhas cujo status é NULL, porque NULL <> 'fechado' não é verdadeiro, é desconhecido. Se você quer essas linhas, precisa de OR status IS NULL. Esta é uma das maiores fontes de relatório com número errado.
  • Qualquer conta com NULL vira NULL. valor_total + frete some se o frete for nulo. Use COALESCE(frete, 0) quando o vazio deve valer zero.
  • NULL, string vazia e zero são três coisas diferentes. NULL = nunca preenchido; '' = preenchido com nada; 0 = preenchido com zero. Tratar os três como "vazio" no teste é como defeito de importação de dados passa despercebido.
  • CASE sem ELSE devolve NULL. Se nenhuma condição bater e não houver ELSE, o resultado é nulo, e a soma daquela coluna some junto.

Prática

Escreva a query e clique Rodar (ou Ctrl+Enter).

carregando banco…

Query livre

Terminou este módulo? Marque: o ✓ aparece no índice da trilha e a barra de progresso anda no começo do site. Fica salvo só no seu navegador.