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

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

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

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

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

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

Или же

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

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

Ответ 5

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

SELECT a / NULLIF(b, 0) FROM t 

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

Ответ 8

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

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

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

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

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

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

Ответ 14

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

Ответ 15

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

Ответ 16

Иногда 0 может не подходить, но иногда 1 также не подходит. Иногда скачок от 0 до 100 000 000, описываемый как 1 или 100-процентное изменение, также может вводить в заблуждение. 100 000 000 процентов могут быть уместны в этом сценарии. Это зависит от того, какие выводы вы намереваетесь сделать, основываясь на процентах или соотношениях.

Например, очень мелко продаваемый товар, переходящий с 2-4 проданных товаров, и очень крупно продаваемый товар, изменяющийся с 1 000 000 на 2 000 000 проданных товаров, могут означать для аналитика или руководства совершенно разные вещи, но оба будут иметь значение 100% или 1. менять.

Возможно, будет проще изолировать значения NULL, чем просматривать группы из 0% или 100% строк, смешанных с допустимыми данными. Зачастую 0 в знаменателе может указывать на ошибку или пропущенное значение, и вы можете не захотеть просто заполнять произвольное значение только для того, чтобы ваш набор данных выглядел аккуратно.

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

Ответ 17

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

Я согласен с Beska в том, что вы не хотите скрывать ошибку. Возможно, вы не имеете дело с ядерным реактором, но скрывать ошибки в целом - плохая практика программирования. Это одна из причин, по которой большинство современных языков программирования реализуют структурированную обработку исключений, чтобы отделить фактическое возвращаемое значение с кодом ошибки/состояния. Это особенно верно, когда вы занимаетесь математикой. Самая большая проблема заключается в том, что вы не можете отличить правильно вычисленное 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.
}

Ответ 18

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

NULLIF(exp,0) ломается, если exp null, но NULLIF(ISNULL(exp,0),0) не сломается

Ответ 19

Вот как я это исправил:

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

Это может быть включено в обновление:

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

Или в избранном:

ВЫБЕРИТЕ IIF (ValueA! = 0, Total/ValueA, 0) AS Pct FROM Tablename;

Мысли?