Как избежать ошибки "деление на ноль" в SQL?

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

Msg 8134, Уровень 16, состояние 1, Линия 1 делится на нулевую ошибку.

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

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

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

или

  • я мог бы добавить оператор case, так что есть особое отношение к нулю.

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

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

17 ответов


чтобы избежать ошибки "деление на ноль", мы запрограммировали ее следующим образом:

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

но вот гораздо более приятный способ сделать это:

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

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


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

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

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


это казалось лучшим решением для моей ситуации при попытке решить деление на ноль, что происходит в моих данных.

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

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, и ошибка не генерируется.


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

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

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


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

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

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

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

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


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

SELECT a / NULLIF(b, 0) FROM t 
, Я никогда преобразовать это в ноль с coalesce как показано в другой ответ, который получил много положительных отзывов. Это совершенно неправильно в математическом смысле, и это даже опасно, поскольку ваше приложение, вероятно, вернет неправильные и вводящие в заблуждение результаты.

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

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


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

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

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

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

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


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

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

  1. добавить ограничение проверки, которое заставляет Divisor не-ноль
  2. добавьте валидатор в форму, чтобы пользователь не мог вводить нулевые значения в это поле.

для обновления SQLs:

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

нет волшебной глобальной настройки "отключить деление на 0 исключений". Операция должна бросать, так как математическое значение x / 0 отличается от значения NULL, поэтому оно не может возвращать NULL. Я предполагаю, что вы заботитесь об очевидном, и ваши запросы имеют условия, которые должны устранить записи с делителем 0 и никогда не оценивать деление. Обычный "gotcha" - это то, что большинство разработчиков ожидают, что SQL будет вести себя как процедурные языки и предлагать логический оператор короткое замыкание, но это не. Я рекомендую вам прочитать эту статью: http://www.sqlmag.com/Articles/ArticleID/9148/pg/2/2.html


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

Я смотрю на вычисление количества оборотов инвентаря, которые происходят в трехмесячный период. Я подсчитал, что у меня есть стоимость товаров, проданных за три месяца в размере 1000$. Годовой темп продаж 4,000 $(1,000$/3)*12. Начало инвентаризации 0. Окончание инвентаризации 0. Мой средний инвентарь теперь равен 0. У меня продажи 4000 долларов в год, а инвентаря нет. Это дает бесконечное число оборотов. Это означает, что все мои запасы преобразуются и покупаются клиентами.

Это бизнес-правило расчета оборачиваемости запасов.


отфильтровать данные с помощью предложения where, чтобы вы не получили 0 значений.


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

вы можете обрабатывать ошибку соответствующим образом, когда она распространяется обратно в вызывающую программу (или игнорировать ее, если это то, что вы хотите). В C# любые ошибки, возникающие в SQL, вызовут исключение, которое я могу поймать, а затем обработать в своем коде, как и любая другая ошибка.

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

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.
}

использовать NULLIF(exp,0) но таким образом -NULLIF(ISNULL(exp,0),0)

NULLIF(exp,0) перерывы, Если exp null но NULLIF(ISNULL(exp,0),0) не сломается