How to protect sql statement from Divide By Zero error - sql-server

I'm in the process of creating some reports that take a finite total (lets say 2,500) of products (for this example lets say Ice Cream Cones) and counts how many of them were broken before serving.
Now the actual count code of broken cones I've got down.
SELECT COUNT(broken_cones) FROM [ice].[ice_cream_inventory]
WHERE broken_cones = 'Yes'
However, I need a percentage of broken cones from this total as well. I've been playing around with the code but I keep running into a 'Divide By Zero' error with this code below.
SELECT CAST(NULLIF((.01 * 2500)/Count(broken_cones), 0) AS
decimal(7,4)) FROM [ice].[ice_cream_inventory] WHERE broken_cones = 'Yes'
For right now, there aren't any broken cones (and won't be for a while) so the total right now is zero. How can I show the NULL scenario as zero?
I tried to place an ISNULL statement in the mix but I kept getting the 'Divide by Zero' error. Am I even doing this right?
::edit::
Here's what I ended up with.
SELECT
CASE
WHEN COUNT(broken_cones) = 0 then 0
ELSE CAST(NULLIF((.01 * 2500)/Count(broken_cones), 0) AS decimal(7,4))
END
FROM [ice].[ice_cream_inventory] WHERE broken_cones = 'Yes'

Use a case statement.
SELECT
CASE WHEN COUNT(broken_cones) = 0 then 0
ELSE CAST(NULLIF((.01 * 2500)/Count(broken_cones), 0) AS decimal(7,4)) END
FROM [ice].[ice_cream_inventory] WHERE broken_cones = 'Yes'

You already have a solution, but this is why your original solution didn't work.
Your NULLIF needs to be moved in order to be effective. It is doing the division before it gets to the NULLIF call. Dividing by null will return a null value.
SELECT CAST((.01 * 2500)/NULLIF(Count(broken_cones), 0) AS decimal(7,4))
FROM [ice].[ice_cream_inventory]
WHERE broken_cones = 'Yes'`

The NULLIF() function is a great way to prevent divide by zero, since anything divide by NULL returns null. The way to use it is as follows:
<expression> / NULLIF( <expression>, 0 )
Unfortunately you've wrapped your whole divide expression in NULLIF() which is why it isn't working for you. So step one is to get it to return NULL if your COUNT() comes back zero:
SELECT
(0.01 * 2500) / NULLIF( COUNT(broken_cones), 0 )
FROM [ice].[ice_cream_inventory]
WHERE broken_cones = 'Yes'
Now you said you wanted that NULL to come back zero? That is where you use ISNULL():
ISNULL(<expression1>, <expression2>)
If the first expression is NULL then return the second expression, so our SQL now becomes:
SELECT
ISNULL(
(0.01 * 2500) / NULLIF( COUNT(broken_cones), 0 ),
0
)
FROM [ice].[ice_cream_inventory]
WHERE broken_cones = 'Yes'

Related

Using between clause on nvarchar column to find range is not working

I have 3 nvarchar columns user_3, user_4 and description. I am setting yes and no flag. If the value in description column is equal or in between user_3 and User4 then set the flag to 'N' else
set the flag to 'Y'.
Here is the SQL script I wrote so far. it works in some instances but not always. See the image with results. it worked on line #1 but didn't work on line # 6 for example. What am I doing wrong?
SELECT [B].USER_3,[B].USER_4,A.DESCRIPTION,
(case when Isnumeric(A.DESCRIPTION) <> 1 then 'Y'
else case when (CASE WHEN Isnumeric(A.DESCRIPTION) = 1 then
cast(A.DESCRIPTION AS decimal(10,5)) else 0 end)
between ( CASE WHEN Isnumeric([B].USER_4) = 1 then
cast([B].USER_4 AS decimal(10,5)) else 0 end) and
(CASE WHEN Isnumeric([B].USER_3) = 1 then cast([B].USER_3 AS decimal(10,5)) else 0 end)
then 'N' else 'Y' end end) as Flagset
from A , B
Here is the screenshot of the results
enter image description here
The issue is with your use of BETWEEN as per the docs:
BETWEEN returns TRUE if the value of test_expression is greater than or equal to the value of begin_expression and less than or equal to the value of end_expression.
Because you don't know whether USER_3 or USER_4 is the higher limit or the lower limit, you need to test both ways.
Note: For this sort of query I prefer to pre-calculate all the values (using CROSS APPLY in this case) I need. It makes it much easier to follow and debug.
SELECT USER_3, USER_4, [DESCRIPTION]
, CASE WHEN ISNUMERIC([DESCRIPTION]) <> 1 THEN 'Y' ELSE
CASE WHEN CASE WHEN ISNUMERIC([DESCRIPTION]) = 1 THEN CAST([DESCRIPTION] AS decimal(10,5)) ELSE 0 END BETWEEN CASE WHEN ISNUMERIC(USER_4) = 1 THEN CAST(USER_4 AS decimal(10,5)) ELSE 0 END AND
CASE WHEN ISNUMERIC(USER_3) = 1 THEN CAST(USER_3 AS decimal(10,5)) ELSE 0 END
THEN 'N' ELSE 'Y' END END AS Flagset
, CASE WHEN DNUMERIC <> 1 THEN 'Y' ELSE CASE WHEN DESCRIPTIOND BETWEEN USER_4D AND USER_3D OR DESCRIPTIOND BETWEEN USER_3D AND USER_4D THEN 'N' ELSE 'Y' END END CorrectedFlagSet
FROM (VALUES
('1.395','1.385','1.390')
, ('22.025','41.425','22')
, ('22.025','41.425','23.025')
) AS X (USER_3, USER_4, [DESCRIPTION])
CROSS APPLY (VALUES (
CASE WHEN ISNUMERIC(USER_3) = 1 THEN CAST(USER_3 AS decimal(10,5)) ELSE 0 END
, CASE WHEN ISNUMERIC(USER_4) = 1 THEN CAST(USER_4 AS decimal(10,5)) ELSE 0 END
, CASE WHEN ISNUMERIC([DESCRIPTION]) = 1 THEN CAST([DESCRIPTION] AS decimal(10,5)) ELSE 0 END
, CASE WHEN ISNUMERIC([DESCRIPTION]) = 1 THEN 1 ELSE 0 END
)) AS Y (USER_3D, USER_4D, DESCRIPTIOND, DNUMERIC);
Returns:
USER_3
USER_4
DESCRIPTION
Flagset
CorrectedFlagSet
1.395
1.385
1.390
N
N
22.025
41.425
22
Y
Y
22.025
41.425
23.025
Y
N
I'm sure I don't have to mention that you should really be storing this data in numeric form in the first place as it will perform better and save you lots of future issues.
And well laid out queries with consistent casing also helping understand and debug them.
Finally providing a minimal reproducible example with sample data, your query and your desired result as shown here makes it much easier for people to assist.

i used nullif and got sql divide by zero error

select STORECODE,
ItemCode,
ColorCode,
ToplamStok,
ToplamSatis,
(CASE WHEN ToplamSatis = 0
THEN ISNULL(ToplamStok/NULLIF(ToplamSatis,0.1)*7,0)
ELSE (ToplamStok/ToplamSatis)*7
end) as SDH
into #SatisStokSDH
from #SatisStok
for this query, I'm getting this error : Divide by zero error encountered.
why am i getting this, i already used isnull function?
Thanks in advance.
Your problem is that when ToplamSatis happens to be zero, you are still dividing by zero:
NULLIF(ToplamSatis, 0.1)
The above would replace ToplamSatis with 0.1, but only if the former were NULL, not if it were zero. Try the following CASE logic:
CASE WHEN ToplamSatis = 0
THEN ISNULL((ToplamStok / 0.1)*7, 0) -- not sure if need to wrap with ISNULL
ELSE (ToplamStok / ToplamSatis)*7
END

TSQL - CAST AS PERCENT

In my select list, I want to return a column that shows the percent of appointments missed. When a client misses an appointment, the source column contains "0" for duration. So I would like to sum the instances of "0" and divide by total appointments scheduled.
CAST(SUM(CASE WHEN event_client_duration = 0 THEN 1 ELSE 0 END)/COUNT(event_key) AS FLOAT)
That does not throw an error, but it returns a value of 0 except in the rare case when appointments missed are more than 50% in which case it returns 1.
SUM(CASE WHEN event_client_duration = 0 THEN 1 ELSE 0 END) -- that works
COUNT(event_key) -- that also works, but together they bonk
So, I tried to cast it as a decimal, but this causes an arithmetic overflow.
CAST(SUM(CASE WHEN e_cl_dur = 0 THEN 1 ELSE 0 END)/COUNT(e_key) AS DECIMAL(2,2))
I also tried using percent as a data type. Any ideas?
SUM(CASE event_client_duration WHEN 0 THEN 1.0 ELSE 0.0 END) / COUNT(event_key)

Conversion failed when converting the nvarchar to int

I have a field which is varchar and contains numbers and dates as strings. I want to update all numbers in this field that is greater than 720. I have attempted firstly to do a select but I get this error:
Conversion failed when converting the nvarchar value '16:00' to data type int.
This is my query:
select id, case(isnumeric([other08])) when 1 then [other08] else 0 end
from CER where sourcecode like 'ANE%' --and other08 > 720
It fails when I uncomment the last part.
I am trying to get all numerics greater than 720, but I can't do the comaprison. It also fails when casting and converting.
Thanks all for any help
You also need to perform the checks and conversion in the WHERE clause:
SELECT
id,
CASE WHEN isnumeric([other08]) = 1 THEN CAST([other08] AS INT) ELSE 0 END
FROM CER
WHERE sourcecode LIKE 'ANE%'
AND CASE WHEN isnumeric([other08]) = 1 THEN CAST([other08] AS INT) ELSE 0 END > 720
You need to use IsNumeric in your where clause, to avoid trying to compare strings to the number 720. Eg:
select id, case(isnumeric([other08])) when 1 then [other08] else 0 end
from CER
where sourcecode like 'ANE%' and ISNUMERIC(other08) = 1 and other08 > 720
EDIT
As #Abs pointed out, the above approach won't work. We can use a CTE to compute a reliable field to filter on, however:
WITH Data AS (
select id
, case WHEN isnumeric([other08]) THEN CAST([other08] AS int) else 0 end AS FilteredOther08
, CER.*
from CER
where sourcecode like 'ANE%'
)
SELECT *
FROM Data
WHERE [FilteredOther08] > 720

Microsoft SQL: CASE WHEN vs ISNULL/NULLIF

Besides readability is there any significant benifit to using a CASE WHEN statement vs ISNULL/NULLIF when guarding against a divide by 0 error in SQL?
CASE WHEN (BeginningQuantity + BAdjustedQuantity)=0 THEN 0
ELSE EndingQuantity/(BeginningQuantity + BAdjustedQuantity) END
vs
ISNULL((EndingQuantity)/NULLIF(BeginningQuantity + BAdjustedQuantity,0),0)
Remember that NULL is different from 0. So the two code snippets in the question can return different results for the same input.
For example, if BeginningQuantity is NULL, the first expression evaluates to NULL:
CASE WHEN (NULL + ?)=0 THEN 0 ELSE ?/(NULL + ?) END
Now (NULL + ?) equals NULL, and NULL=0 is false, so the ELSE clause is evaluated, giving ?/(NULL+?), which results in NULL. However, the second expression becomes:
ISNULL((?)/NULLIF(NULL + ?,0),0)
Here NULL+? becomes NULL, and because NULL is not equal to 0, the NULLIF returns the first expression, which is NULL. The outer ISNULL catches this and returns 0.
So, make up your mind: are you guarding against divison by zero, or divison by NULL? ;-)
In your example I think the performance is negligible. But in other cases, depending on the complexity of your divisor, the answer is 'it depends'.
Here is an interesting blog on the topic:
For readability, I like the Case/When.
In my opinion, using Isnull/Nullif is faster than using Case When. I rather the isnull/nullif.
I would use the ISNULL, but try to format it so it shows the meaning better:
SELECT
x.zzz
,x.yyyy
,ISNULL(
EndingQuantity / NULLIF(BeginningQuantity+BAdjustedQuantity,0)
,0)
,x.aaa
FROM xxxx...
CASE WHEN (coalesce(BeginningQuantity,0) + coalesce(BAdjustedQuantity,0))=0 THEN 0 ELSE coalesce(EndingQuantity,0)/(coalesce(BeginningQuantity,0) + coalesce(BAdjustedQuantity,0)) END
your best option imho
Sorry, here is the little more simplify upbuilded sql query.
SELECT
(ISNULL([k1],0) + ISNULL([k2],0)) /
CASE WHEN (
(
CASE WHEN [k1] IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN [k2] IS NOT NULL THEN 1 ELSE 0 END
) > 0 )
THEN
(
CASE WHEN [k1] IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN [k2] IS NOT NULL THEN 1 ELSE 0 END
)
ELSE 1 END
FROM dbo.[Table]

Resources