Как избежать ошибки «делить на ноль» в SQL? - PullRequest
314 голосов
/ 14 мая 2009

У меня есть это сообщение об ошибке:

Сообщение 8134, уровень 16, состояние 1, строка 1: ошибка деления на ноль.

Как лучше написать SQL-код, чтобы я больше никогда не видел это сообщение об ошибке?

Я мог бы сделать одно из следующих действий:

  • Добавить предложение where, чтобы мой делитель никогда не был равен нулю

Или

  • Я мог бы добавить оператор case, чтобы был специальный режим для нуля.

Является ли лучший способ использовать предложение NULLIF?

Есть ли лучший способ или как это можно применить?

Ответы [ 18 ]

565 голосов
/ 14 мая 2009

Чтобы избежать ошибки «Деление на ноль», мы запрограммировали ее так:

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

Но вот гораздо лучший способ сделать это:

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

Теперь единственная проблема - запомнить бит NullIf, если я использую клавишу "/".

158 голосов
/ 04 января 2012

Если вы хотите вернуть ноль, в случае возникновения нулевого отклонения вы можете использовать:

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

Для каждого делителя, равного нулю, вы получите ноль в наборе результатов.

60 голосов
/ 17 декабря 2013

Это, кажется, лучшее решение для моей ситуации, когда я пытаюсь решить проблему деления на ноль, что происходит в моих данных.

Предположим, вы хотите вычислить соотношение мужчин и женщин для различных школьных клубов, но обнаруживаете, что следующий запрос не выполняется и выдает ошибку деления на ноль, когда он пытается вычислить соотношение для клуба Властелина колец, не имеет женщин:

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

Вы можете использовать функцию NULLIF, чтобы избежать деления на ноль. NULLIF сравнивает два выражения и возвращает ноль, если они равны, или первое выражение в противном случае.

Перепишите запрос как:

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

Любое число, деленное на NULL, дает NULL, и ошибка не генерируется.

40 голосов
/ 30 июня 2011

Вы также можете сделать это в начале запроса:

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

Так что если у вас есть что-то вроде 100/0, он вернет NULL. Я сделал это только для простых запросов, поэтому не знаю, как это повлияет на более длинные / сложные запросы.

33 голосов
/ 26 ноября 2015

Вы можете, по крайней мере, остановить прерывание запроса с ошибкой и вернуть NULL, если есть деление на ноль:

SELECT a / NULLIF(b, 0) FROM t 

Однако я бы НИКОГДА не преобразовал бы это в Ноль с coalesce, как это показано в другом ответе, который получил много голосов. Это совершенно неверно в математическом смысле и даже опасно, поскольку ваше приложение, скорее всего, даст неверные и вводящие в заблуждение результаты.

32 голосов
/ 14 мая 2009

EDIT: В последнее время я получаю много отрицательных отзывов ... так что я решил добавить примечание, что этот ответ был написан до того, как вопрос подвергся последнему редактированию, где возвращаемый ноль был выделен как опция ... кажется очень приемлемым. Некоторые из моих ответов были адресованы таким же проблемам, как и Эдвардо, в комментариях, которые, похоже, выступали за возвращение 0. Это случай, против которого я выступал.

Ответ: Я думаю, что здесь есть основная проблема, которая заключается в том, что деление на 0 не является законным. Это признак того, что что-то в корне неправильно. Если вы делите на ноль, вы пытаетесь сделать что-то, что не имеет смысла математически, поэтому никакой числовой ответ, который вы можете получить, не будет действительным. (Использование нуля в этом случае разумно, так как это не значение, которое будет использоваться в последующих математических вычислениях).

Таким образом, Эдвардо спрашивает в комментариях «что если пользователь вводит 0?», И он настаивает на том, что все должно быть в порядке, чтобы получить 0 взамен. Если пользователь помещает ноль в сумму, и вы хотите, чтобы 0 возвращалось, когда они это делают, то вам нужно вставить код на уровне бизнес-правил, чтобы перехватить это значение и вернуть 0 ... не должно быть какого-то особого случая, когда деление на 0 = 0.

Это тонкое различие, но оно важно ... потому что в следующий раз, когда кто-то вызовет вашу функцию и ожидает, что она будет делать правильные вещи, и он сделает что-то напуганное, что не математически правильно, а просто обрабатывает конкретный крайний случай у него есть хороший шанс укусить кого-нибудь позже. Вы на самом деле не делите на 0 ... вы просто возвращаете плохой ответ на плохой вопрос.

Представь, что я что-то кодирую, и я облажался. Я должен был прочитать значение масштабирования измерения радиации, но в странном граничном случае я не ожидал, я прочитал в 0. Затем я опускаю свое значение в вашу функцию ... вы возвращаете мне 0! Ура, нет излучения! За исключением того, что это действительно там, и это просто, что я передал плохую ценность ... но я понятия не имею. Я хочу, чтобы подразделение выдавало ошибку, потому что это признак того, что что-то не так.

22 голосов
/ 26 августа 2009
SELECT Dividend / ISNULL(NULLIF(Divisor,0), 1) AS Result from table

Улавливая ноль с помощью nullif (), затем полученный ноль с помощью isnull () вы можете обойти деление на ноль ошибок.

9 голосов
/ 24 апреля 2018

Замена «делить на ноль» на ноль противоречива - но это также не единственный вариант. В некоторых случаях замена на 1 является (разумно) целесообразной. Я часто использую

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

когда я смотрю на сдвиги в счетах / счетчиках и хочу установить значение по умолчанию 1, если у меня нет данных. Например

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

Чаще всего я фактически вычислял это соотношение где-то еще (не в последнюю очередь потому, что оно может генерировать очень большие поправочные коэффициенты для низких знаменателей. В этом случае я обычно контролирую для OldSampleScore больше порога; затем исключает ноль. Но иногда «взлом» уместен.

6 голосов
/ 14 мая 2009

Некоторое время назад я написал функцию для ее обработки для моих хранимых процедур :

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
4 голосов
/ 16 сентября 2015

Для обновления SQL:

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