Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como obter um restante usando MOD() no PostgreSQL, MS SQL Server e MySQL

Problema:


Você quer encontrar o resto (não negativo).

Exemplo:


Na tabela numbers , você tem duas colunas de inteiros:a e b .
a b
9 3
5 3
2 3
0 3
-2 3
-5 3
-9 3
5 -3
-5 -3
5 0
0 0

Você deseja calcular os restos da divisão de a por b . Cada resto deve ser um valor inteiro não negativo menor que b .

Solução 1 (não totalmente correta):

SELECT
  a,
  b,
  MOD(a, b) AS remainder
FROM numbers;

O resultado é:
a b restante
9 3 0
5 3 2
2 3 2
0 3 0
-2 3 -2
-5 3 -2
-9 3 0
5 -3 2
-5 -3 -2
5 0 erro
0 0 erro

Discussão:


Esta solução funciona corretamente se a for não negativo. No entanto, quando é negativo, não segue a definição matemática do resto.

Conceitualmente, um resto é o que resta após uma divisão inteira de a por b . Matematicamente, um resto de dois inteiros é um inteiro não negativo que é menor que o divisor b . Mais precisamente, é um número r∈{0,1,...,b - 1} para o qual existe algum inteiro k tal que a =k * b + r . Por exemplo.:

5 = 1 * 3 + 2 , então o restante de 5 e 3 é igual a 2 .

9 = 3 * 3 + 0 , então o restante de 9 e 3 é igual a 0 .

5 = (-1) * (-3) + 2 , então o restante de 5 e -3 é igual a 2 .

É assim que MOD(a, b) funciona para os dividendos não negativos na coluna a . Obviamente, um erro é mostrado se o divisor b é 0 , porque você não pode dividir por 0 .

Obter o resto correto é problemático quando o dividendo a é um número negativo. Infelizmente, MOD(a, b) pode retornar um valor negativo quando a é negativo. Por exemplo.:

MOD(-2, 5) retorna -2 quando deve retornar 3 .

MOD(-5, -3) retorna -2 quando deve retornar 1 .

Solução 2 (correta para todos os números):

SELECT
  a,
  b,
  CASE WHEN MOD(a, b) >= 0
    THEN MOD(a, b)
  ELSE
    MOD(a, b) + ABS(b)
  END AS remainder
FROM numbers;

O resultado é:
a b restante
9 3 0
5 3 2
2 3 2
0 3 0
-2 3 1
-5 3 1
-9 3 0
5 -3 2
-5 -3 1
5 0 erro
0 0 erro

Discussão:


Para calcular o restante de uma divisão entre qualquer dois inteiros (negativos ou não negativos), você pode usar o CASE WHEN construção. Quando MOD(a, b) é não negativo, o resto é simplesmente MOD(a, b) . Caso contrário, temos que corrigir o resultado retornado por MOD(a, b) .

Como você obtém o resto correto quando MOD() retorna um valor negativo? Você deve adicionar o valor absoluto do divisor a MOD(a, b) . Ou seja, faça MOD(a, b) + ABS(b) :

MOD(-2, 5) retorna -2 quando deve retornar 3 . Você pode corrigir isso adicionando 5 .

MOD(-5, -3) retorna -2 quando deve retornar 1 . Você pode corrigir isso adicionando 3 .

Quando MOD(a, b) retorna um número negativo, o CASE WHEN o resultado deve ser MOD(a, b) + ABS(b) . É assim que obtemos a Solução 2. Se você precisar de uma atualização sobre como o ABS() funciona, dê uma olhada no livro de receitas Como calcular um valor absoluto em SQL.

Claro, você ainda não pode dividir nenhum número por 0 . Então, se b = 0 , você receberá um erro.

Solução 3 (correta para todos os números):

SELECT
  a,
  b,
  MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2 AS remainder
FROM numbers;

O resultado é:
a b restante
9 3 0
5 3 2
2 3 2
0 3 0
-2 3 1
-5 3 1
-9 3 0
5 -3 2
-5 -3 1
5 0 erro
0 0 erro

Discussão:


Existe outra maneira de resolver este problema. Em vez de um CASE WHEN , use uma fórmula matemática de uma linha mais complexa:

MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2

Na Solução 2, MOD(a, b) + ABS(b) foi retornado para casos em que MOD(a, b) < 0 . Observe que MOD(a, b) + ABS(b) = MOD(a, b) + ABS(b) * 1 when MOD(a, b) < 0 .

Por outro lado, você retorna MOD(a, b) quando MOD(a, b) >= 0 . Observe que MOD(a, b) = MOD(a, b) + ABS(b) * 0 when MOD(a, b) >= 0 .

Assim, podemos multiplicar ABS(b) por uma expressão que é igual a 1 para um MOD(a, b) negativo e 0 para um MOD(a, b) não negativo . Desde MOD(a, b) é sempre um inteiro, a expressão MOD(a, b) + 0.5 é sempre positivo para MOD(a, b) ≥ 0 e negativo para MOD(a, b) < 0 . Você pode usar qualquer número positivo menor que 1 em vez de 0.5 .

A função de sinal SIGN() retorna 1 se seu argumento for estritamente positivo, -1 se for estritamente negativo e 0 se for igual a 0 . No entanto, você precisa de algo que retorne apenas 0 e 1 , não 1 e -1 . Aqui está como você corrige isso:

(1 - 1) / 2 = 0

(1 - (-1)) / 2 = 1

Então, a expressão correta pela qual você multiplica ABS(b) é:

(1 - SIGN(MOD(a, b) + 0.5)) / 2

Então, a fórmula inteira é:

MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2