CASE WHEN: lógica condicional no SQL

1. Introdução ao CASE WHEN

A expressão CASE WHEN é a implementação padrão ANSI SQL para lógica condicional, permitindo que bancos de dados executem decisões baseadas em condições específicas durante consultas. Diferentemente de linguagens de programação tradicionais (como IF/ELSE em Python ou Java), o CASE WHEN opera diretamente no conjunto de dados, avaliando condições para cada linha retornada.

Enquanto funções como COALESCE tratam especificamente de valores nulos (retornando o primeiro valor não nulo) e NULLIF compara dois valores (retornando NULL se forem iguais), o CASE WHEN oferece flexibilidade total para definir qualquer condição booleana. Essa distinção é crucial: COALESCE e NULLIF resolvem problemas pontuais, enquanto CASE WHEN é a ferramenta genérica para lógica condicional.

2. Sintaxe Básica do CASE WHEN

Existem duas formas principais de CASE WHEN:

CASE Simples — compara uma expressão com valores fixos:

SELECT 
    nome,
    CASE status
        WHEN 'A' THEN 'Ativo'
        WHEN 'I' THEN 'Inativo'
        WHEN 'P' THEN 'Pendente'
        ELSE 'Desconhecido'
    END AS status_descricao
FROM clientes;

CASE Pesquisado — avalia condições booleanas arbitrárias:

SELECT 
    produto,
    preco,
    CASE 
        WHEN preco < 50 THEN 'Barato'
        WHEN preco BETWEEN 50 AND 200 THEN 'Médio'
        WHEN preco > 200 THEN 'Caro'
        ELSE 'Sem preço'
    END AS categoria_preco
FROM produtos;

A ordem de avaliação é sequencial: a primeira condição verdadeira define o resultado. O ELSE é opcional, mas sua ausência retorna NULL quando nenhuma condição é satisfeita.

3. Usando CASE WHEN em SELECT

Categorizando valores de uma coluna:

SELECT 
    aluno,
    nota,
    CASE 
        WHEN nota >= 90 THEN 'Excelente'
        WHEN nota >= 70 THEN 'Bom'
        WHEN nota >= 50 THEN 'Regular'
        ELSE 'Insuficiente'
    END AS classificacao
FROM notas;

Colunas calculadas condicionais:

SELECT 
    nome,
    salario,
    CASE 
        WHEN cargo = 'Gerente' THEN salario * 1.20
        WHEN cargo = 'Analista' THEN salario * 1.10
        ELSE salario * 1.05
    END AS salario_reajustado
FROM funcionarios;

Combinando com funções de agregação:

SELECT 
    departamento,
    COUNT(*) AS total_funcionarios,
    SUM(CASE WHEN salario > 5000 THEN 1 ELSE 0 END) AS funcionarios_acima_5k,
    AVG(CASE WHEN YEAR(data_admissao) = 2023 THEN salario ELSE NULL END) AS media_salario_novatos
FROM funcionarios
GROUP BY departamento;

4. CASE WHEN em Cláusulas WHERE e ORDER BY

Filtrando com condições dinâmicas no WHERE:

SELECT 
    pedido_id,
    data_pedido,
    valor_total
FROM pedidos
WHERE 
    CASE 
        WHEN EXTRACT(YEAR FROM data_pedido) = 2023 THEN valor_total > 1000
        WHEN EXTRACT(YEAR FROM data_pedido) = 2024 THEN valor_total > 500
        ELSE valor_total > 200
    END;

Ordenação personalizada com CASE no ORDER BY:

SELECT 
    tarefa,
    prioridade,
    data_vencimento
FROM tarefas
ORDER BY 
    CASE prioridade
        WHEN 'Urgente' THEN 1
        WHEN 'Alta' THEN 2
        WHEN 'Média' THEN 3
        WHEN 'Baixa' THEN 4
        ELSE 5
    END,
    data_vencimento ASC;

Este exemplo ordena tarefas por prioridade personalizada, depois por data de vencimento ascendente.

5. CASE WHEN em Atualizações e Inserções (UPDATE / INSERT)

Atualizando valores condicionalmente:

UPDATE produtos
SET preco = CASE 
    WHEN categoria = 'Eletrônicos' THEN preco * 0.90
    WHEN categoria = 'Roupas' THEN preco * 0.80
    WHEN estoque < 10 THEN preco * 0.95
    ELSE preco
END;

Inserindo dados derivados:

INSERT INTO historico_precos (produto_id, preco_antigo, preco_novo, data_alteracao)
SELECT 
    id,
    preco,
    CASE 
        WHEN promocao = 'S' THEN preco * 0.85
        ELSE preco
    END,
    CURRENT_DATE
FROM produtos;

Cuidados com performance: Em operações em lote com milhões de registros, CASE WHEN pode ser mais eficiente que múltiplas consultas UPDATE separadas, mas sempre avalie o plano de execução.

6. CASE WHEN Aninhado e Combinações Avançadas

CASE WHEN aninhado para lógica complexa:

SELECT 
    cliente_id,
    CASE 
        WHEN total_compras > 10000 THEN 
            CASE 
                WHEN frequencia_compras > 20 THEN 'VIP Premium'
                ELSE 'VIP'
            END
        WHEN total_compras > 5000 THEN 'Ouro'
        WHEN total_compras > 1000 THEN 'Prata'
        ELSE 'Bronze'
    END AS nivel_fidelidade
FROM clientes;

Combinando com subconsultas e JOINs:

SELECT 
    p.nome,
    p.preco,
    CASE 
        WHEN p.preco > (SELECT AVG(preco) FROM produtos) THEN 'Acima da média'
        ELSE 'Abaixo ou igual à média'
    END AS comparacao_media
FROM produtos p
LEFT JOIN categorias c ON p.categoria_id = c.id;

Pivotando dados (transformar linhas em colunas):

SELECT 
    departamento,
    SUM(CASE WHEN mes = 'Janeiro' THEN vendas ELSE 0 END) AS jan,
    SUM(CASE WHEN mes = 'Fevereiro' THEN vendas ELSE 0 END) AS fev,
    SUM(CASE WHEN mes = 'Março' THEN vendas ELSE 0 END) AS mar
FROM vendas_mensais
GROUP BY departamento;

7. Boas Práticas e Armadilhas Comuns

Evitando ambiguidade com ELSE e tratamento de nulos:

-- PROBLEMA: NULL não é capturado por nenhum WHEN
SELECT 
    CASE 
        WHEN valor > 0 THEN 'Positivo'
        WHEN valor < 0 THEN 'Negativo'
        -- ELSE implícito retorna NULL para valor = 0 ou NULL
    END

-- SOLUÇÃO: ELSE explícito e tratamento de NULL
SELECT 
    CASE 
        WHEN valor > 0 THEN 'Positivo'
        WHEN valor < 0 THEN 'Negativo'
        WHEN valor = 0 THEN 'Zero'
        ELSE 'Sem valor'
    END

Performance: CASE WHEN geralmente é eficiente, mas para substituições simples de valores, funções específicas do banco (como DECODE no Oracle ou IIF no SQL Server) podem ser mais rápidas. Teste com seu SGBD.

Legibilidade: Para lógica simples de substituição de nulos, prefira COALESCE:

-- Menos legível
SELECT CASE WHEN nome IS NULL THEN 'Sem nome' ELSE nome END

-- Mais legível
SELECT COALESCE(nome, 'Sem nome')

8. Exemplos Práticos no PostgreSQL

Categorizar vendas por trimestre com CASE + funções de data:

SELECT 
    EXTRACT(YEAR FROM data_venda) AS ano,
    CASE 
        WHEN EXTRACT(MONTH FROM data_venda) BETWEEN 1 AND 3 THEN 'Q1'
        WHEN EXTRACT(MONTH FROM data_venda) BETWEEN 4 AND 6 THEN 'Q2'
        WHEN EXTRACT(MONTH FROM data_venda) BETWEEN 7 AND 9 THEN 'Q3'
        ELSE 'Q4'
    END AS trimestre,
    SUM(valor) AS total_vendas,
    COUNT(*) AS quantidade_vendas
FROM vendas
GROUP BY ano, trimestre
ORDER BY ano, trimestre;

Classificar usuários por nível de atividade com NULLIF:

SELECT 
    usuario_id,
    CASE 
        WHEN NULLIF(ultimo_acesso, primeiro_acesso) IS NULL THEN 'Nunca retornou'
        WHEN ultimo_acesso > CURRENT_DATE - INTERVAL '30 days' THEN 'Ativo'
        WHEN ultimo_acesso > CURRENT_DATE - INTERVAL '90 days' THEN 'Inativo'
        ELSE 'Abandonou'
    END AS status_atividade
FROM usuarios;

Relatório de desempenho com CASE e GROUP BY:

SELECT 
    vendedor_id,
    COUNT(*) AS total_vendas,
    SUM(CASE WHEN valor > 1000 THEN 1 ELSE 0 END) AS vendas_acima_1k,
    ROUND(
        100.0 * SUM(CASE WHEN valor > 1000 THEN 1 ELSE 0 END) / COUNT(*), 
        2
    ) AS percentual_vendas_alto_valor,
    CASE 
        WHEN SUM(CASE WHEN valor > 1000 THEN 1 ELSE 0 END) > 50 THEN 'Performance Excelente'
        WHEN SUM(CASE WHEN valor > 1000 THEN 1 ELSE 0 END) > 20 THEN 'Performance Boa'
        ELSE 'Performance Regular'
    END AS avaliacao
FROM vendas
WHERE data_venda >= '2024-01-01'
GROUP BY vendedor_id
HAVING COUNT(*) > 10
ORDER BY percentual_vendas_alto_valor DESC;

Referências