sqlpostgresqlmathfunctions

SIGN no SQL: o sinal de um numero e ramificacao por direcao

Como SIGN retorna -1/0/1, como ramificar pela direcao da mudanca e por que combinar SIGN com ABS.

2 min de leituraReferencesql · postgresql · math · functions · analytics

SIGN retorna o sinal de um numero: -1 para negativos, 0 para zero e 1 para positivos. E uma funcao minuscula, mas ela transforma o "quanto" em "para que lado" e reduz pilhas de logica CASE desajeitada a uma unica chamada.

O que SIGN retorna

A assinatura e trivial: entra um numero e sai um de tres valores -1, 0, 1.

SELECT SIGN(-42)   AS neg,   -- -1
       SIGN(0)     AS zero,  --  0
       SIGN(17.5)  AS pos;   --  1

O tipo do resultado espelha o do argumento: numeric entra e numeric sai, double precision da double precision. Se o argumento for NULL, o resultado tambem e NULL: nao e um quarto estado, apenas a propagacao normal do nulo.

Um uso pratico e normalizar os valores dos pedidos ate a direcao do fluxo de caixa, ignorando a magnitude:

SELECT id,
       amount,
       SIGN(amount) AS direction  -- -1 refund, 0 zero, 1 charge
FROM orders;

Ramificar pela direcao da mudanca

Na maioria das vezes voce recorre a SIGN para classificar a diferenca entre dois valores como "subiu", "caiu" ou "igual". Vamos comparar o valor de um pedido com o pedido anterior do mesmo usuario por meio de uma funcao de janela:

SELECT id,
       user_id,
       amount,
       SIGN(amount - LAG(amount) OVER (
           PARTITION BY user_id ORDER BY created_at
       )) AS trend  -- -1 down, 0 flat, 1 up
FROM orders;

Os tres valores se desdobram com limpeza em rotulos usando CASE. Repare que o ramo WHEN 0 captura a igualdade exata, e nao um "quase igual":

SELECT id,
       CASE SIGN(amount - 100)
           WHEN -1 THEN 'below target'
           WHEN  0 THEN 'on target'
           WHEN  1 THEN 'above target'
       END AS bucket
FROM orders;

SIGN com ABS: a direcao separada da magnitude

Qualquer numero se fatora como x = SIGN(x) * ABS(x). Separar a direcao da magnitude e a principal razao para manter SIGN por perto. Por exemplo, somar o que saiu como reembolsos contra o que entrou como cobrancas em uma unica passagem:

SELECT SUM(CASE WHEN SIGN(amount) = 1 THEN amount ELSE 0 END) AS charged,
       SUM(CASE WHEN SIGN(amount) = -1 THEN ABS(amount) ELSE 0 END) AS refunded
FROM orders;

A mesma combinacao ajuda ao ordenar pela proximidade de um alvo: ordene por ABS (quao longe) e mantenha o sinal a parte para mostrar de que lado um salario fica em relacao a uma referencia do departamento:

SELECT name,
       dept,
       salary,
       SIGN(salary - 60000) AS side,   -- which way off
       ABS(salary - 60000)  AS gap     -- how far off
FROM employees
ORDER BY gap DESC;

Estabilidade numerica e pegadinhas

SIGN em si e estavel: nunca perde precisao nem estoura, porque so entrega tres valores. O perigo se esconde no argumento.

  • Zero de ponto flutuante. Em um calculo 0.0 pode chegar como um minusculo 1e-18, e ai SIGN retorna 1, nao 0. Se o ramo do zero importa, arredonde antes: SIGN(ROUND(x::numeric, 6)).
  • NULL escapa por todos os ramos. Em CASE SIGN(x) nem -1, nem 0, nem 1 combinam com um NULL: adicione um IS NULL explicito ou um ELSE.
  • Divisao inteira. SIGN(a / b) com inteiros divide primeiro: SIGN(3 / 4) e SIGN(0) = 0. Converta para um tipo fracionario antes de dividir.

As diferencas entre os motores sao pequenas, mas vale conhece-las:

  • PostgreSQL retorna o mesmo tipo numerico do argumento.
  • MySQL tem a mesma funcao SIGN(), mas sempre retorna um inteiro -1/0/1.
  • ClickHouse oferece sign() (em minusculas) com o mesmo comportamento -1/0/1.
-- Pitfall: integer division hides the real sign
SELECT SIGN(3 / 4)               AS wrong,  -- 0
       SIGN(3.0 / 4)             AS right_; -- 1

Lembre da identidade x = SIGN(x) * ABS(x): ela resolve metade das perguntas de "subiu ou caiu" sem um unico CASE aninhado.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador