Divide by Zero Error encountered in Snowflake Query - snowflake-cloud-data-platform

I have the following snowflake query where I am getting a divide by zero error...Can you please help me here..
with cte1 as
(select * from "coe.cup"
where typeofcare ='AM'
and status ='DONE'
and review ='false'
and date (assigneddate)>='2021-04-01'), cte2 as(
select cast(completed as date) completeddate ,iscode
,iff(iscode=1,datediff(minute,assigneddate,coded),0) codeddatetime
,iff(iscode=0,datediff(minute,assigneddate,qaed),0) qaeddatetime
,datediff(minute,assigneddate,completed) overall from
(select *,iff(qaed='1900-01-01 00:00:00.0000000',1,0) iscode from cte1)a )
select completeddate
,sum(iff(iscode=1,1,0)) noofvisitbillscoded
,sum(iff(iscode=1,0,1)) noofvisitbillscodedandqaed
,count(1) totalvisitbillscompleted
,cast(sum(codeddatetime)/sum(iff(iscode=1,1,0)) as float)/60 averagetimeforcodedvisitbills
,cast(sum(qaeddatetime)/sum(iff(iscode=1,0,1)) as float)/60 averagetimeforcodedandqaedvisitbills
,cast(sum(overall)/count(1) as float)/60 overallaveragetime
from cte2
group by completeddate

Another option is to to use the DIV0 function so something like:
DIV0(sum(codeddatetime),sum(iff(iscode=1,1,0))
More info here: https://docs.snowflake.com/en/sql-reference/functions/div0.html

When division is involved the divisor could be handled with NULLIFZERO:
NULLIFZERO( )
Returns NULL if the argument evaluates to 0; otherwise, returns the argument.
Second pattern sum(iff(iscode=1,1,0)) is a conditional sum that emulates filtered COUNT. It could be further simplfied with COUNT_IF
COUNT_IF( )
Returns the number of records that satisfy a condition.
To sum up:
,cast(sum(codeddatetime)/sum(iff(iscode=1,1,0)) as float)/60
=>
,SUM(codeddatetime)/NULLIFZERO(COUNT_IF(iscode=1))/60
=> if iscode is boolean column then:
,SUM(codeddatetime)/NULLIFZERO(COUNT_IF(iscode))/60

I assume that this happens due to the /sum(iff(iscode=1,1,0)) where this presumably sometimes returns 0.
One aproach to deal with division by zero is to use NULLIF
NULLIF( <expr1> , <expr2> )
returns NULL if expr1 is equal to expr2, otherwise returns expr1.
So, in your code where you have, for example sum(iff(iscode=1,1,0)), you can replace this with:
NULLIF(sum(iff(iscode = 1, 1, 0)), 0)
and this should then return NULL instead of causing a division by zero.

Related

SQL CAST causes Arithmetic Overflow Error even when row is NOT in result set

This one has us perplexed ...
We have a query that uses CAST to convert a float to a decimal, this query joins a number of tables to find the rows to return. One of the rows in one of the tables contains a value that when CAST to a decimal causes an Arithmetic Overflow Error.
The strange thing is that the row that has this value is NOT one of the rows that is being returned in the result set.
Overly simplified example:
ID Value
1 1.1
2 11.1
3 11111.1
Query:
SELECT Id, CAST(value as decimal(4,1))
FROM <complex number of joins>
WHERE <conditions that don't return row with Id 3>
... Arithmetic Error
If we explicitly exclude that row in the WHERE clause then the error goes away.
Eg. WHERE ... AND Id <> 3
.. Works fine
Does anyone know how this is possible?
NOTE: The issue here is not that the CAST fails on row with Id 3!
The issue is that the WHERE clause excludes the row with Id 3, and yet the query still fails. How can the query fail if the row with value 11111.1 is not being returned by the WHERE clause?
The type DECIMAL(4, 1) means a total of four places of precision, one of which is to the right of the decimal place. So, to accommodate the value 11111.1, you would need at least DECIMAL(6, 1). The following query should work:
SELECT Id, CAST(value AS DECIMAL(6,1))
FROM <complex number of joins>
WHERE <conditions that don't return row with Id 3>
At least, the above would work for the three points of sample data you provided.
Demo
It seems that the filter is being applied after the operation, not the other way around. Take a look at the execution plan for your query to help you understand the order of operations.
it is not because of your where condition which filters data, but it is because you have chosen less data length in cast. You should change it to DECIMAL(8,2) or maximum length of your column data is there. You can try following example which will explain you how it works.
Following will work as it doesn't fetch any data
WITH yourTable AS (
SELECT 1 AS ID, '1.1' AS Value UNION ALL
SELECT 2, '11.1' UNION ALL
SELECT 3, '11111.1313'
)
SELECT Id, CAST(value as decimal(4,1)) AS Id_casted
FROM yourTable WHERE yourTable.ID=4
Following won't work as decimal value exceed than conversion length
WITH yourTable AS (
SELECT 1 AS ID, '1.1' AS Value UNION ALL
SELECT 2, '11.1' UNION ALL
SELECT 3, '11111.1313'
)
SELECT Id, CAST(value as decimal(4,1)) AS Id_casted
FROM yourTable WHERE yourTable.ID=3
You can solve this by changing Decimal(4,1) = 3 digit to Decimal(8,2) = 6 digit
WITH yourTable AS (
SELECT 1 AS ID, '1.1' AS Value UNION ALL
SELECT 2, '11.1' UNION ALL
SELECT 3, '11111.1313'
)
SELECT Id, CAST(value as DECIMAL(8,2)) AS Id_casted
FROM yourTable WHERE yourTable.ID=3
Simply following code shows it throws exception as max value will be 999.99 for numeric(5,2) and when you assign 1000 it will throw exception
DECLARE #aritherror NUMERIC(5,2)
SET #aritherror = 1000.554
SELECT #aritherror

Finding point of interest on a square wave using sql

Good day,
I have a sql table with the following setup:
DataPoints{ DateTime timeStampUtc , bit value}
The points are on a minute interval, and store either a 1(on) or a 0(off).
I need to write a stored procedure to find the points of interest from all the data points.
I have a simplified drawing below:
I need to find the corner points only. Please note that there may be many data points between a value change. For example:
{0,0,0,0,0,0,0,1,1,1,1,0,0,0}
This is my thinking atm (high level)
Select timeStampUtc, Value
From Data Points
Where Value before or value after differs by 1 or -1
I am struggling to convert this concept to sql, and I also have a feeling there is an more elegant mathematical solution that I am not aware off. This must be a common problem in electronics?
I have wrapped the table into a CTE. Then, I am joining every row in the CTE to the next row of itself. Also, I've added a condition that the consequent rows should differ in the value.
This would return you all rows where the value changes.
;WITH CTE AS(
SELECT ROW_NUMBER() OVER(ORDER BY TimeStampUTC) AS id, VALUE, TIMESTAMPUTC
FROM DataPoints
)
SELECT CTE.TimeStampUTC as "Time when the value changes", CTE.id, *
FROM CTE
INNER JOIN CTE as CTE2
ON CTE.id = CTE2.id + 1
AND CTE.Value != CTE2.Value
Here's a working fiddle: http://sqlfiddle.com/#!6/a0ddc/3
If I got it correct, you are looking for something like this:
with cte as (
select * from (values (1,0),(2,0),(3,1),(4,1),(5,0),(6,1),(7,0),(8,0),(9,1)) t(a,b)
)
select
min(a), b
from (
select
a, b, sum(c) over (order by a rows unbounded preceding) grp
from (
select
*, iif(b = lag(b) over (order by a), 0, 1) c
from
cte
) t
) t
group by b, grp

Execution of ISNULL in SQL Server

Here is how i am using ISNULL condition to check for student address.
It works fine but how ISNULL function treat the null codition i.e the second parameter which is display if first condition is null.
Will it calculate Value for second parameter when first condition is not null?
select
...
...
(CASE
WHEN st.ADDRESS='Y' THEN st.LOCATION
ELSE
ISNULL(
(SELECT TOP 1 STDLOC.LOCATION FROM STDLOC
INNER JOIN COMLOC ON STKLOC.LOCATION=COMLOC.CODE AND COMLOC.ADDRESS='Y'
WHERE STDLOC.ZIBCODE=st.ZIBCODE)
,(SELECT TOP 1 COMLOC.LOCATION FROM COMLOC COMLOC.ZIBCODE=st.ZIBCODE))
END
) AS STDUDENTLOCATION
FROM STUDENT st
Both queries inside the ISNULL will be executed, even if the first query will return a value.
Here is a simple test I've made:
Create and populate sample table:
DECLARE #T AS TABLE
(
Col int
)
INSERT INTO #T Values(1),(2)
SELECT ISNULL(
(SELECT TOP 1 Col FROM #T ORDER BY Col DESC),
(SELECT TOP 1 Col FROM #T ORDER BY Col )
)
Execution plan image:
As you can clearly see, the execution plan includes both queries.
I also was looking for an answer. After some reading I came out with my own way to check it.
Deviding by zero will give an error, so we can try:
SELECT ISNULL( (SELECT TOP 1 object_id FROM sys.columns), 5 / 0)
This will give correct result. BUT
SELECT ISNULL( (SELECT TOP 0 object_id FROM sys.columns), 5 / 0)
It will throw an error, because result of first query gives NULL so it tries the second query which fails
ISNULL is a T-SQL specific function that will use the specified second parameter as the return value if the first parameter is NULL(https://msdn.microsoft.com/en-us/library/ms184325.aspx).
Use COALESCE function if you want to return the first non-null value from multiple arguments, and this is a standard function that is supported by all types of relational databases.
This POST provide a good answer for the question:
Is Sql Server's ISNULL() function lazy/short-circuited?

Can i use a case statement to convert a varchar to decimal and use that in my where clause?

I have a column which I want to convert to decimal so I can then use it to compare in my where clause. I want to make sure all values from the column are greater or equal to 1.3. I converted the column successfully in the select statement but when attempting to do the same convert in the where clause I get the following error:
Arithmetic overflow error converting varchar to data type numeric.
I am using SQL Server 2008.
SELECT ID,
CASE
WHEN ISNUMERIC(USER_3) = 1
THEN Convert(varchar(50), CONVERT(decimal(14,2), USER_3))
END AS KG_M
FROM PART
WHERE USER_3 IS NOT NULL
AND CASE
WHEN ISNUMERIC(USER_3) = 1
THEN Convert(varchar(50), CONVERT(decimal(14,2), USER_3))
END >= 1.3
Sure, why not? Here's a self-contained example:
select a.ID
, b.KG_M
from (values
(1, N'12345678')
, (2, N'ABCDEFGH')
) as a (ID, USER_3)
cross apply (values(
case IsNumeric(a.USER_3)
when 1 then Convert(varchar(50), Convert(decimal(14, 2), a.USER_3))
else a.USER_3
end
)) as b (KG_M)
where b.KG_M >= '1.3';
We simply use the APPLY operator to contain our calculation for reuse later.
You need to choose one way to convert. I would use the native type for comparison, decimal.
SELECT * FROM
(
SELECT ID, KG_M=CAST(USER_3 AS decimal(14,2))
FROM PART
WHERE
ISNUMERIC(USER_3) = 1
)AS X
WHERE
X.KG_M >= 1.3
Allow strings that are not numbers in outoput
SELECT * FROM
(
SELECT
ID,
USER_3_AsDecimal=CASE WHEN ISNUMERIC(USER_3) THEN CAST(USER_3 AS decimal(14,2)) ELSE NULL END,
USER_3
FROM PART
WHERE
NOT USER_3 IS NULL
)AS X
WHERE
X.USER_3_AsDecimal IS NULL
OR
X.USER_3_AsDecimal >= 1.3
The problem was a syntax error, the case in the where clause was a success the entire time.
"you should use >= '1.3' since you are converting to varchar" credit to #Lamak in comments

Closest numeric value - but always return lower value

My question is very similar to this: Find closest numeric value in database
However, to take the example used in the link above, how could this code be modified so that the lowest matching value is returned. So 1.6 would return 1.5, but also 1.8 would return 1.5?
Thanks in advance
Mark
I haven't tried it, but how about
SELECT TOP 1 [property] FROM [table]
WHERE [valueColumn] < [input]
ORDER BY [input] - [valueColumn]
?
This way it will only take values lower than the input and order by their "distance" to the input.
You can try with this code - based on ASC and TOP(2) operators
SELECT TOP (2) YourProperty FROM [myTable]
WHERE (condition)
ORDER BY YourProperty ASC
Cleaner version of #AsgerArentoft answer in my opinion:
SELECT TOP 1 * FROM [table]
WHERE [valueColumn] <= [input]
ORDER BY [valueColumn] desc
<= is important because if valueColumn has values 1,3,5 and input is 3, given < then the returned value would be 1.

Resources