Como evitar o erro "dividir por zero" no SQL?


363

Eu tenho esta mensagem de erro:

Msg 8134, Nível 16, Estado 1, Linha 1 Erro de divisão por zero encontrado.

Qual é a melhor maneira de escrever código SQL para que eu nunca mais veja essa mensagem de erro?

Eu poderia fazer um dos seguintes:

  • Adicione uma cláusula where para que meu divisor nunca seja zero

Ou

  • Eu poderia adicionar uma declaração de caso, para que haja um tratamento especial para zero.

É a melhor maneira de usar uma NULLIFcláusula?

Existe uma maneira melhor ou como isso pode ser aplicado?


7
Talvez alguma validação de dados esteja em ordem.
184 Anthony

Respostas:


645

Para evitar um erro "Divisão por zero", nós o programamos assim:

Select Case when divisor=0 then null
Else dividend / divisor
End ,,,

Mas aqui está uma maneira muito melhor de fazer isso:

Select dividend / NULLIF(divisor, 0) ...

Agora, o único problema é lembrar o bit NullIf, se eu usar a tecla "/".


13
Uma maneira muito mais agradável de fazer isso "Selecionar dividendo / nulo (divisor, 0) ..." é interrompido se o divisor for NULL.
Anderson

8
@ Anderson Isso não é verdade. Tem certeza de que não usou acidentalmente em IsNullvez de NullIf? Tente você mesmo! SELECT Value,1/NullIf(Value,0)FROM(VALUES(0),(5.0),(NULL))x(Value);A menos que por "quebras" você queira dizer retorne um NULL? Você pode converter isso para o que quiser com IsNullou Coalesce.
precisa saber é o seguinte

11
@ErikE, é verdade ... tente executar ... selecione 1 / nullif (null, 0) ... você obtém "O tipo do primeiro argumento para NULLIF não pode ser a constante NULL porque o tipo do primeiro argumento tem ser conhecido." Resolva isso usando "coalesce (FieldName, 0)" ... por exemplo, selecione 1 / nullif (coalesce (null, 0), 0)
John Joseph

11
@ JohnJoseph Não sei dizer se você está concordando comigo ou discutindo comigo.
ErikE

2
@JohnJoseph Veja mais de perto o erro que você recebeu. Sim, SELECT 1 / NULLIF(NULL, 0)falha, mas é porque NULLIF()precisa conhecer o tipo de dados do primeiro argumento. Este exemplo alterado funciona bem: SELECT 1 / NULLIF(CAST(NULL AS INT), 0). Na vida real, você fornecerá uma coluna da tabela em NULLIF()vez de uma NULLconstante. Desde colunas da tabela possuem tipos de dados conhecidos, isso também funciona bem: SELECT 1 / NULLIF(SomeNullableColumn, 0) FROM SomeTable.
MarcelCheese

180

Caso deseje retornar zero, caso ocorra um desvio zero, você pode usar:

SELECT COALESCE(dividend / NULLIF(divisor,0), 0) FROM sometable

Para cada divisor que é zero, você receberá um zero no conjunto de resultados.


9
Alguns benchmarks revelam que COALESCE é um pouco mais lento que ISNULL. No entanto, o COALESCE está nos padrões, portanto é mais portátil.
Paul Chernoch

37
Se alguém não entender imediatamente por que isso funciona, NULLIF (d, 0) retornará NULL se d for 0. No SQL, dividir por NULL retornará NULL. O Coalesce substitui o NULL resultante por 0.
GuiSim 16/04

16
@SQLGeorge Embora eu concorde com seu argumento, observe que há casos em que se importa mais o que é estatisticamente correto do que matematicamente correto. Em alguns casos, ao usar funções estatísticas, 0 ou até 1 é um resultado aceitável quando o divisor é zero.
Athafoud 03/02

10
Alguém pode me explicar por que isso é ruim? Se estou tentando descobrir um por cento e o divisor é zero, certamente quero que o resultado seja zero por cento.
Todd afiada

10
Penso que @ George e @ James / Wilson interpretam mal a pergunta que está sendo feita. Certamente, existem aplicativos de negócios nos quais o retorno de um "0" é apropriado, mesmo que não seja tecnicamente verdadeiro do ponto de vista matemático.
Sean Branchaw 27/09/16

66

Essa parecia ser a melhor correção para minha situação ao tentar resolver a divisão por zero, o que acontece nos meus dados.

Suponha que você queira calcular as proporções homem-mulher para vários clubes da escola, mas descobre que a consulta a seguir falha e emite um erro de divisão por zero quando tenta calcular a proporção para o Clube do Senhor dos Anéis, que não tem mulheres :

SELECT club_id, males, females, males/females AS ratio
  FROM school_clubs;

Você pode usar a função NULLIFpara evitar a divisão por zero. NULLIFcompara duas expressões e retorna nulo se forem iguais ou a primeira expressão de outra forma.

Reescreva a consulta como:

SELECT club_id, males, females, males/NULLIF(females, 0) AS ratio
  FROM school_clubs;

Qualquer número dividido por NULLdoa NULL, e nenhum erro é gerado.


6
Sim, de fato, isso é MUITO MELHOR do que a outra resposta que tem tantos votos positivos. Na sua solução, você tem pelo menos um NULL, o que indica que você não pode fornecer um resultado correto. Mas se você converter o resultado de NULL para Zero, simplesmente terá resultados errados e enganosos.
SQL Police

8
By the way, se você quiser calcular uma relação masculino / feminino, então eu sugiro que a melhor compará-lo ao total, como este: select males/(males+females), females/(males+females). Isso fornecerá a distribuição percentual de homens e mulheres em um clube, como 31% homens e 69% mulheres.
SQL Police

44

Você também pode fazer isso no início da consulta:

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

Portanto, se você tiver algo parecido 100/0, retornará NULL. Eu fiz isso apenas para consultas simples, então não sei como isso afetará as mais longas / complexas.


11
Funciona para mim. No meu caso, eu tenho que usar a operação de divisão na cláusula WHERE. Tenho certeza de que não há um divisor de zero, porque quando eu comento WHERE out, não há valores zero nos resultados. Mas, de alguma forma, o otimizador de consultas é dividido por zero durante a filtragem. SET ARITHABORT OFF SET e ANSI_WARNINGS OFF funcionam - após 2 dias de luta com divisão por zero na cláusula WHERE. Valeu!
precisa saber é o seguinte

2
Isso "parece" tão sujo, mas eu adoro! Necessário em uma consulta que agrega e usa a instrução CASE não era uma opção, porque então eu tive que adicionar essa coluna ao GROUP BY que alterou totalmente os resultados. Tornar a consulta inicial uma subseleção e, em seguida, fazer um GROUP BY na consulta externa também altera os resultados porque há uma divisão envolvida.
Andrew Steitz

11
OK, então eu ainda gosto dessa "solução", mas como muitos de vocês provavelmente sentiram, senti que tinha que haver uma maneira "mais limpa". E se eu esquecer de reativar os avisos? Ou alguém cloanhou meu código (isso nunca acontece, certo?) E não pensou nos avisos? Enfim, vi outras respostas sobre NULLIF (). Eu sabia sobre NULLIF (), mas não percebi que dividir por NULL retorna NULL (pensei que seria um erro). Então ... eu fui com o seguinte: ISNULL ((SUM (foo) / NULLIF (SUM (bar), 0)), 0) AS Avg
Andrew Steitz

2
Eu não conhecia esta solução. Não sei se gosto, mas pode ser útil conhecer um dia. Muito obrigado.
Henrik Staun Poulsen

11
Essa é a solução mais fácil, mas observe que isso prejudicará o desempenho. De docs.microsoft.com/en-us/sql/t-sql/statements/… : "Definir ARITHABORT como OFF pode afetar negativamente a otimização de consultas, causando problemas de desempenho".
mono Blaine

36

Você pode pelo menos impedir que a consulta seja interrompida por um erro e retornar NULLse houver uma divisão por zero:

SELECT a / NULLIF(b, 0) FROM t 

No entanto, NUNCA converteria isso em Zero, coalescecomo mostra a outra resposta que recebeu muitos votos. Isso é completamente errado no sentido matemático e é até perigoso, pois seu aplicativo provavelmente retornará resultados errados e enganosos.


32

Edição: Estou recebendo muitas votações negativas sobre isso recentemente ... então pensei em adicionar uma nota de que esta resposta foi escrita antes da pergunta ser submetida à edição mais recente, onde retornar nulo foi destacado como uma opção .. .que parece muito aceitável. Parte da minha resposta foi dirigida a preocupações como a de Edwardo, nos comentários, que pareciam estar defendendo o retorno de um 0. É esse o caso contra o qual eu estava reclamando.

RESPOSTA: Eu acho que há uma questão subjacente aqui, que é que a divisão por 0 não é legal. É uma indicação de que algo está errado fundamentalmente. Se você está dividindo por zero, está tentando fazer algo que não faz sentido matematicamente, para que nenhuma resposta numérica que você possa obter seja válida. (O uso de null neste caso é razoável, pois não é um valor que será usado em cálculos matemáticos posteriores).

Então, Edwardo pergunta nos comentários "e se o usuário colocar um 0?", E ele defende que não há problema em obter um 0 em troca. Se o usuário colocar zero no valor e você desejar 0 retornado quando fizer isso, deverá inserir um código no nível das regras de negócios para capturar esse valor e retornar 0 ... não terá algum caso especial em que a divisão por 0 = 0

Essa é uma diferença sutil, mas é importante ... porque da próxima vez que alguém chamar sua função e esperar que ela faça a coisa certa, faça algo descolado que não seja matematicamente correto, mas que lide apenas com o caso específico, boa chance de morder alguém mais tarde. Você não está realmente dividindo por 0 ... está apenas retornando uma resposta ruim para uma pergunta ruim.

Imagine que estou codificando algo e estrago tudo. Eu deveria estar lendo em um valor de escala de medição de radiação, mas em um caso estranho que não previ, li em 0. Depois, coloco meu valor em sua função ... você me retorna 0! Viva, sem radiação! Só que está realmente lá e é que eu estava passando com um valor ruim ... mas não faço ideia. Quero que a divisão gere o erro, porque é a bandeira de que algo está errado.


15
Discordo. Suas regras de negócios nunca devem acabar fazendo matemática ilegal. Se você acabar fazendo algo assim, provavelmente seu modelo de dados está errado. Sempre que você encontrar uma divisão por 0 você deve ponderar se os dados devem tinha sido NULL em vez de 0.
Remus Rusanu

32
Não acredito que fui votado por alguém que pergunta se alguma vez "fiz alguma programação real?" porque estou dizendo para fazer certo, em vez de ser preguiçoso. sigh
Beska #

11
Me desculpe, eu não quis te ofender. Mas a pergunta é perfeitamente válida em muitos aplicativos LOB comuns, e respondê-la com uma "divisão por 0 não é legal" não agrega valor ao IMHO.
Eduardo Molteni

2
@JackDouglas Right. É um caso em que você deseja que as regras de negócios tratem um caso especial de uma maneira especial ... mas não deve ser a matemática subjacente que retorna um espaço em branco. Deve ser a regra de negócios. A resposta aceita retorna um nulo, que é uma boa maneira de lidar com isso. Lançar uma exceção também seria bom. Identificá-lo e manipulá-lo antes de ir para o SQL seria sem dúvida o ideal. Fornecer algum tipo de função que outras coisas poderiam chamar que retornasse um valor matematicamente incorreto não é o caminho a seguir, porque esse caso especial pode não se aplicar a esses outros chamadores.
Beska

4
@JackDouglas Sim, esse é um bom resumo, com o qual concordo. Originalmente, a pergunta parecia ser formulada como "o que posso fazer para esconder esse erro". Desde então, ele evoluiu. Retornar um nulo, a resposta a que ele finalmente chega, parece uma resposta razoável. (I foi fortemente defendendo não retornando a 0, ou algum outro número.)
Beska

28
SELECT Dividend / ISNULL(NULLIF(Divisor,0), 1) AS Result from table

Ao capturar o zero com um nullif (), o nulo resultante com um isnull (), você pode contornar sua divisão por erro zero.


3
Devido ao seu comprimento, sua resposta foi recomendada para exclusão. Note-se que é sempre melhor para adicionar uma pequena explicação sobre o que você está sugerindo - mesmo se parece muito simples;)
Trinimon

10

Substituir "dividir por zero" por zero é controverso - mas também não é a única opção. Em alguns casos, a substituição por 1 é (razoavelmente) apropriada. Costumo me encontrar usando

 ISNULL(Numerator/NULLIF(Divisor,0),1)

quando estou observando mudanças nas pontuações / contagens e quero usar como padrão 1 se não tiver dados. Por exemplo

NewScore = OldScore *  ISNULL(NewSampleScore/NULLIF(OldSampleScore,0),1) 

Na maioria das vezes, na verdade, eu calculei essa proporção em algum outro lugar (principalmente porque pode gerar alguns fatores de ajuste muito grandes para denominadores baixos. Nesse caso, eu normalmente controlaria o OldSampleScore é maior que um limite; o que então impede zero Mas às vezes o 'hack' é apropriado.


11
@N Mason; sim, às vezes 1 é uma opção. Mas como você se lembra da parte ISNULL, quando você digita a barra invertida?
Henrik Staun Poulsen

11
Desculpe Henrik - não sei se entendi a pergunta.
N Mason

6

Escrevi uma função há algum tempo para lidar com ela nos meus procedimentos armazenados :

print 'Creating safeDivide Stored Proc ...'
go

if exists (select * from dbo.sysobjects where  name = 'safeDivide') drop function safeDivide;
go

create function dbo.safeDivide( @Numerator decimal(38,19), @divisor decimal(39,19))
   returns decimal(38,19)
begin
 -- **************************************************************************
 --  Procedure: safeDivide()
 --     Author: Ron Savage, Central, ex: 1282
 --       Date: 06/22/2004
 --
 --  Description:
 --  This function divides the first argument by the second argument after
 --  checking for NULL or 0 divisors to avoid "divide by zero" errors.
 -- Change History:
 --
 -- Date        Init. Description
 -- 05/14/2009  RS    Updated to handle really freaking big numbers, just in
 --                   case. :-)
 -- 05/14/2009  RS    Updated to handle negative divisors.
 -- **************************************************************************
   declare @p_product    decimal(38,19);

   select @p_product = null;

   if ( @divisor is not null and @divisor <> 0 and @Numerator is not null )
      select @p_product = @Numerator / @divisor;

   return(@p_product)
end
go

2
Olá Ron, Boa solução, exceto que possui um tipo de dados limitado (4 casas decimais) e nossos divisores também podem ser negativos. E como você aplica seu uso? TIA Henrik Staun Poulsen
Henrik Staun Poulsen

11
Eu corri rapidamente para lidar com um cenário de problema específico na época. Aplicativo de desenvolvedor único, portanto, a aplicação não é tão difícil, exceto pela minha memória. :-)
Ron Savage

5
Apesar da declaração de impressão, não é um processo armazenado, é um UDF escalar. Isso o matará no MS-SQL se fizer parte de uma consulta.
Mark Sowul

4
Concordei com a afirmação de Mark Sowul de que a função escalar causará dor. Esta é uma péssima sugestão no T-SQL, não faça isso! Funções escalares são destruidoras de desempenho! As funções com valor de tabela em linha são as únicas boas funções de usuário no SQL Server (possivelmente com exceção das funções CLR que podem ter bom desempenho).
Davos

4
  1. Adicione uma restrição CHECK que obriga Divisora ser diferente de zero
  2. Adicione um validador ao formulário para que o usuário não possa inserir valores zero nesse campo.

11
Começo a gostar das restrições CHECK cada vez mais.
Henrik Staun Poulsen 16/08/10

4

Para SQLs de atualização:

update Table1 set Col1 = Col2 / ISNULL(NULLIF(Col3,0),1)

3
oi Vijay, Sim, isso funcionará, mas ... Eu teria cuidado com a parte ISNULL, onde você acaba dividindo por NULL. Prefiro sinalizar ao usuário que o resultado é desconhecido porque o divisor é zero.
Henrik Staun Poulsen

2
Isso me salvou em uma subconsulta complicada, obrigado.
QMaster 20/02

3

Não existe uma configuração mágica global 'desativar divisão por 0 exceções'. A operação deve ser lançada, pois o significado matemático de x / 0 é diferente do significado NULL, portanto, não pode retornar NULL. Suponho que você esteja cuidando do óbvio e que suas consultas tenham condições que devem eliminar os registros com o divisor 0 e nunca avaliar a divisão. A 'pegadinha' usual é que a maioria dos desenvolvedores espera que o SQL se comporte como linguagens procedurais e ofereça um curto-circuito lógico ao operador, mas NÃO . Eu recomendo que você leia este artigo: http://www.sqlmag.com/Articles/ArticleID/9148/pg/2/2.html


4
Existe uma "configuração global mágica": DESLIGUE O ARITHABORT.
David Manheim

3

Aqui está uma situação em que você pode dividir por zero. A regra de negócios é que, para calcular as rotações de estoque, você tome o custo dos produtos vendidos por um período, anualize-o. Depois de ter o número anualizado, você divide pelo estoque médio do período.

Estou pensando em calcular o número de giros de estoque que ocorrem em um período de três meses. Calculei que tenho o Custo dos produtos vendidos durante o período de três meses de US $ 1.000. A taxa anual de vendas é de US $ 4.000 (US $ 1.000 / 3) * 12. O estoque inicial é 0. O estoque final é 0. Meu estoque médio agora é 0. Tenho vendas de US $ 4000 por ano e nenhum estoque. Isso produz um número infinito de turnos. Isso significa que todo o meu inventário está sendo convertido e comprado pelos clientes.

Esta é uma regra comercial de como calcular as rotações de estoque.


3
Sim, você tem um número infinito de turnos. Portanto, neste caso, se você tiver uma divisão por zero, deverá mostrar algo como '#INF'.
SQL Police

11
"O inventário inicial é 0. O inventário final é 0. Meu inventário médio agora é 0". Seu cálculo é uma estimativa. Em algum momento, o estoque é positivo ou você não pode enviar / vender nada. Se você não estiver satisfeito com + ∞, use uma estimativa melhor do estoque médio.
precisa saber é o seguinte

2
CREATE FUNCTION dbo.Divide(@Numerator Real, @Denominator Real)
RETURNS Real AS
/*
Purpose:      Handle Division by Zero errors
Description:  User Defined Scalar Function
Parameter(s): @Numerator and @Denominator

Test it:

SELECT 'Numerator = 0' Division, dbo.fn_CORP_Divide(0,16) Results
UNION ALL
SELECT 'Denominator = 0', dbo.fn_CORP_Divide(16,0)
UNION ALL
SELECT 'Numerator is NULL', dbo.fn_CORP_Divide(NULL,16)
UNION ALL
SELECT 'Denominator is NULL', dbo.fn_CORP_Divide(16,NULL)
UNION ALL
SELECT 'Numerator & Denominator is NULL', dbo.fn_CORP_Divide(NULL,NULL)
UNION ALL
SELECT 'Numerator & Denominator = 0', dbo.fn_CORP_Divide(0,0)
UNION ALL
SELECT '16 / 4', dbo.fn_CORP_Divide(16,4)
UNION ALL
SELECT '16 / 3', dbo.fn_CORP_Divide(16,3)

*/
BEGIN
    RETURN
        CASE WHEN @Denominator = 0 THEN
            NULL
        ELSE
            @Numerator / @Denominator
        END
END
GO

Não gosto da sua solução, porque o uso de um UDF força a consulta a ser executada em um único modo de thread. Eu gosto da sua configuração de teste. Eu gostaria de ter isso em todas as nossas UDFs.
Henrik Staun Poulsen 9/09/16

Para mim, esta solução é perfeito e elegante
Payedimaunt

@Payedimaunt; sim, UDFs levam a um código muito elegante. Mas isso não funciona bem. Isso é "cursores em pílulas para dormir". :-) Também é difícil lembrar de escrever dbo.Divide em vez de um "/" comum
Henrik Staun Poulsen

1

Filtre os dados usando uma cláusula where para que você não obtenha 0 valores.


1

Às vezes, 0 pode não ser apropriado, mas às vezes 1 também não é apropriado. Às vezes, um salto de 0 a 100.000.000 descrito como alteração de 1 ou 100% também pode ser enganoso. 100.000.000 por cento podem ser apropriados nesse cenário. Depende de que tipo de conclusões você pretende tirar com base nas porcentagens ou proporções.

Por exemplo, um item de venda muito pequena que passa de 2 a 4 vendidos e um item de venda muito grande que muda de 1.000.000 para 2.000.000 vendidos pode significar coisas muito diferentes para um analista ou para a gerência, mas ambos seriam 100% ou 1. mudança.

Pode ser mais fácil isolar valores NULL do que vasculhar um monte de 0% ou 100% de linhas misturadas com dados legítimos. Freqüentemente, um 0 no denominador pode indicar um erro ou valor ausente, e talvez você não queira apenas preencher um valor arbitrário apenas para tornar seu conjunto de dados organizado.

CASE
     WHEN [Denominator] = 0
     THEN NULL --or any value or sub case
     ELSE [Numerator]/[Denominator]
END as DivisionProblem

11
Acho que o problema é lembrar de fazer algo sempre que você quiser fazer uma divisão. Se você não se lembrar de adicionar CASE ou NULLIF, receberá um caso de suporte em x semanas, na segunda-feira de manhã. Eu odeio isso.
Henrik Staun Poulsen 28/11

1

Foi assim que eu consertei:

IIF (ValorA! = 0, Total / ValorA, 0)

Pode ser agrupado em uma atualização:

SET Pct = IIF (ValorA! = 0, Total / ValorA, 0)

Ou em uma seleção:

SELECT IIF (ValorA! = 0, Total / ValorA, 0) AS Pct FROM Nome da tabela;

Pensamentos?


Eu e outros descobrimos que substituir "desconhecido" por zero é uma solução perigosa. Eu prefiro muito NULL. Mas o difícil é lembrar de adicionar iif ou nullif em todas as divisões!
Henrik Staun Poulsen

0

Você pode manipular o erro adequadamente quando ele se propaga de volta ao programa de chamada (ou ignorá-lo, se é isso que você deseja). No C #, qualquer erro que ocorra no SQL lançará uma exceção que eu posso capturar e depois manipular no meu código, como qualquer outro erro.

Concordo com Beska no sentido de que você não deseja ocultar o erro. Você pode não estar lidando com um reator nuclear, mas esconder erros em geral é uma prática ruim de programação. Essa é uma das razões pelas quais as linguagens de programação mais modernas implementam o tratamento de exceções estruturadas para dissociar o valor de retorno real com um código de erro / status. Isto é especialmente verdade quando você está fazendo contas. O maior problema é que você não pode distinguir entre um 0 computado corretamente sendo retornado ou um 0 como resultado de um erro. Em vez disso, qualquer valor retornado é o valor calculado e, se algo der errado, uma exceção será lançada. Obviamente, isso será diferente dependendo de como você está acessando o banco de dados e qual idioma está usando, mas você sempre deve receber uma mensagem de erro com a qual pode lidar.

try
{
    Database.ComputePercentage();
}
catch (SqlException e)
{
    // now you can handle the exception or at least log that the exception was thrown if you choose not to handle it
    // Exception Details: System.Data.SqlClient.SqlException: Divide by zero error encountered.
}

11
Acho que todos concordamos que ocultar o erro com 0 não é uma solução. O que proponho é escrever nosso código de modo que todo "/" seja seguido por um "NULLIF". Dessa forma, meu relatório / reator nuclear não é deixado sozinho, mas está mostrando um "NULL" em vez do erro traumático Msg 8134. Por uma causa, se eu precisar de um processo diferente quando o divisor for 0, Beska e eu concordamos. Precisamos codificar isso. É difícil fazer toda vez que você escreve um "/". "/ NULLIF" não é impossível de ser feito todas as vezes.
Henrik Staun Poulsen

0

Use NULLIF(exp,0)mas desta maneira -NULLIF(ISNULL(exp,0),0)

NULLIF(exp,0)quebra se exp é, nullmas NULLIF(ISNULL(exp,0),0)não vai quebrar


11
Não consigo obter NULLIF (exp, 0), se 0 for um zero, não um o. Tentar; DECLARE @i INT SELECT 1 / NULLIF (@i, 0) para ver se consegue quebrá-lo.
Henrik Staun Poulsen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.