QA Learning / PostgreSQL

Módulo 4: Texto

||, ILIKE, LOWER/UPPER/INITCAP, LENGTH, TRIM, LEFT/RIGHT, SPLIT_PART, POSITION, REPLACE

Antes de começar

Texto sujo é meio ambiente natural do QA: espaço sobrando, caixa trocada, e-mail com domínio errado. O Postgres tem um bom kit pra investigar isso.

  • a || b: junta textos: nome || ' (' || estado || ')'. Se um lado for NULL o resultado é NULL (use COALESCE).
  • ILIKE: LIKE sem ligar pra maiúsc./minúsc. ~ / ~* fazem regex (sensível / insensível).
  • Caixa: LOWER, UPPER, INITCAP ("joão silva" → "João Silva").
  • Recortar: LEFT(t, 3), RIGHT(t, 4), SUBSTRING(t FROM 2 FOR 5), SPLIT_PART(email, '@', 2) (pega o pedaço 2, o domínio).
  • Achar/trocar: POSITION('@' IN email) (0 = não achou), REPLACE(t, 'a', 'b'), TRIM(t), LENGTH(t).

Olhar de QA: "quantos e-mails não são do domínio esperado?", "tem nome com espaço no fim?", "busca deveria ser case-insensitive e não é".

Dois exemplos

Teoria

  • a || b || c: concatena. CONCAT_WS(', ', a, b) ignora NULL e põe separador.
  • t ILIKE '%abc%': contém "abc" em qualquer caixa; t ~* 'abc' é regex insensível.
  • LOWER / UPPER / INITCAP(t): caixa.
  • SPLIT_PART(email, '@', 2): domínio. LEFT/RIGHT(t, n): pontas.
  • POSITION('x' IN t): índice de "x" (0 = ausente). LENGTH(t): tamanho.
  • TRIM(t) tira espaços das pontas; t = TRIM(t) é falso quando sobra espaço.

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.

|| e CONCAT_WS

SELECT
  nome || ' - ' || estado         AS rotulo,
  CONCAT_WS(', ', cidade, estado)  AS local
FROM clientes;

|| gruda textos; se qualquer pedaço for NULL, o resultado inteiro vira NULL. CONCAT_WS(sep, ...) junta com um separador e ignora os NULL, ficando mais seguro pra montar endereço.

ILIKE e regex ~*

SELECT titulo
FROM defeitos
WHERE titulo ILIKE '%login%'
   OR titulo ~* 'senha|password';

ILIKE '%login%' = contém "login" em qualquer caixa. ~* 'senha|password' é regex insensível a caixa: casa "senha" ou "password". Regex serve quando o padrão é mais complexo que um simples "contém".

LOWER / UPPER / INITCAP

SELECT
  nome,
  LOWER(email)  AS email_normalizado,
  INITCAP(nome) AS nome_padronizado
FROM clientes;

LOWER/UPPER forçam a caixa toda; INITCAP deixa a primeira letra de cada palavra maiúscula. Comparar sempre com a mesma caixa (LOWER(email) = LOWER(entrada)) evita bug de "achou" vs "não achou" por causa de maiúscula.

SPLIT_PART, LEFT / RIGHT

SELECT
  email,
  SPLIT_PART(email, '@', 2) AS dominio,
  LEFT(email, 3)            AS inicio
FROM clientes;

SPLIT_PART(texto, '@', 2) quebra o texto no @ e pega o 2º pedaço: o domínio do email. LEFT(t, n)/RIGHT(t, n) pegam os n primeiros/últimos caracteres.

POSITION, LENGTH

SELECT
  email,
  POSITION('@' IN email) AS pos_arroba,
  LENGTH(email)          AS tamanho
FROM clientes
WHERE POSITION('@' IN email) = 0;   -- email sem @

POSITION('x' IN t) devolve em que caractere "x" aparece, ou 0 se não aparece, útil pra validar formato. LENGTH(t) é o número de caracteres.

TRIM e espaço sobrando

SELECT id, cidade
FROM clientes
WHERE cidade <> TRIM(cidade);   -- tem espaço nas pontas

TRIM(t) remove espaços do começo e do fim. Se cidade for diferente de TRIM(cidade), é porque sobrou espaço, um problema clássico de dado "sujo" que quebra comparação e agrupamento.

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.

  • A armadilha clássica: filtrar a tabela da direita no WHERE transforma o LEFT JOIN num INNER JOIN, em silêncio.
    -- ERRADO: vira INNER JOIN e perde os clientes sem pedido
    SELECT c.nome, p.id
    FROM clientes c
    LEFT JOIN pedidos p ON p.cliente_id = c.id
    WHERE p.status = 'entregue';
    
    -- CERTO: a condição vai no ON
    SELECT c.nome, p.id
    FROM clientes c
    LEFT JOIN pedidos p ON p.cliente_id = c.id AND p.status = 'entregue';
    Por quê: o LEFT JOIN preenche com NULL quem não tem par, e o WHERE logo em seguida descarta essas linhas, porque NULL = 'entregue' não é verdadeiro. O resultado parece certo e está faltando gente. Regra: condição sobre a tabela da direita vai no ON; a única exceção é WHERE x IS NULL, que é justamente como se procura "quem não tem".
  • JOIN sem ON multiplica tudo. Esquecer a condição gera o produto cartesiano: 200 pedidos × 60 clientes = 12 000 linhas. Se o resultado veio absurdamente grande, é a primeira coisa a conferir.
  • JOIN com relação um-para-muitos duplica as linhas da esquerda. Juntar pedidos com itens_pedido repete o pedido uma vez por item. Aí SUM(p.valor_total) soma o mesmo pedido várias vezes e infla o total. É o erro de número mais difícil de perceber, porque o resultado parece plausível.
  • Sempre qualifique a coluna quando o nome existe nas duas tabelas. id sozinho num JOIN de pedidos com clientes é ambíguo: o banco reclama, ou pior, escolhe um.
  • Confira a contagem antes e depois do JOIN. Se pedidos tem 200 linhas e o seu JOIN devolveu 512, houve duplicação. Esse hábito de dois segundos evita relatório errado.

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.