How to solve Divide by Zero exception in SQL stored procedure? - sql-server-2005

When I am executing my stored procedure it is throwing Divide By zero Exception. Because In my database there is no data for some months. How can I solve this?
Below is the my query.
#diffNSVY FLOAT = 0 --I have declared #diffNSVY as optional parameter.
SET #diffNSVY = CASE
WHEN (select top 1 NSV from #temp where rn = 1) < 0 THEN 0
ELSE (((select top 1 NSV from #temp where descrn = 1) - (select top 1 NSV from #temp where rn = 1))*1.0)/(select top 1 NSV from #temp where rn = 1)
END
I am inserting a result set into #temp table. NSV is a column name. rn is rownumber.descrn is also row number in decreasing order.
How can I modify my query?
Please reply.
Regards,
NSJ

First, I would rebuild your script so I didn't need to repeat complex expressions (the subselects, to be precise) more than once.
If possible, use SELECT instead of SET, like this:
SELECT #diffNSVY = CASE
WHEN rn.NSV < 0 THEN 0
ELSE (descrn.NSV - rn.NSV) * 1.0 / rn.NSV /* extra '()' are removed as unneeded */
FROM
(select top 1 NSV from #temp where rn = 1) AS rn,
(select top 1 NSV from #temp where descrn = 1) AS descrn
Next, ask yourself, what the result should be in case of division by zero. Should it be zero as well? Then the next optimisation step would be simply this:
SELECT #diffNSVY = CASE
WHEN rn.NSV <= 0 THEN 0 /* changed '<' to '<=' to account for division by zero */
ELSE (descrn.NSV - rn.NSV) * 1.0 / rn.NSV
FROM
(select top 1 NSV from #temp where rn = 1) AS rn,
(select top 1 NSV from #temp where descrn = 1) AS descrn
But if you wish the result to become undefined (NULL) so you process it later, here's how you can achieve this:
SELECT #diffNSVY = CASE
WHEN rn.NSV < 0 THEN 0
ELSE (descrn.NSV - rn.NSV) * 1.0 / CASE rn.NSV WHEN 0 THEN NULL ELSE rn.NSV END
FROM
(select top 1 NSV from #temp where rn = 1) AS rn,
(select top 1 NSV from #temp where descrn = 1) AS descrn
Generally, I find this pattern useful when I need to secure myself from divide-by-zero errors. I often use it like this:
...ISNULL(some_expression / CASE #Value WHEN 0 THEN NULL ELSE #Value END, 0)...
Sometimes I use it without ISNULL in which case I process the possible NULL result later using some more sophisticated logic.
EDIT: Oh, and one more thing: with SELECT you can have several assignments at once, like this:
SELECT
#Var1 = expression1,
#Var2 = expression2,
...
Could this possibly help you to simplify your query too?

That expression of yours is very unclear and hard to understand, and you're selecting the same value several times which is totally unnecessary - so my recommendation would be:
try to first determine all the bits and pieces that might go into your calcuation - put the results of those select top 1 .... queries into variables
then check before you divide by zero, and if you divisor would be zero, you need to think of another solution / another value to use instead...
Your problem is this: you're currently only checking for your one value being < 0 and then your return 0 - otherwise (including when that value is = 0) you return an expression which is a division by exactly that value.... You need to add one more special case: if that value is = 0, you cannot use your expression since that results in the divide by zero exception - you need to return some other value in this case!
So your code would be something like:
DECLARE #diffNSVY FLOAT = 0 --I have declared #diffNSVY as optional parameter.
DECLARE #RNValue INT
SET #RNValue = (SELECT TOP 1 NSV FROM #temp WHERE rn = 1)
DECLARE #DescRNValue INT
SET #DescRNValue = (SELECT TOP 1 NSV FROM #temp WHERE descrn = 1)
SET #diffNSVY =
CASE
WHEN #RNValue < 0 THEN 0
WHEN #RNValue = 0 THEN ....... <-- you need to define a value here! CAnnot divide by #RNValue if it's ZERO !
ELSE ((#DescRNValue - #RNValue) * 1.0) / #RNValue
END

Maybe you need a '<=0' not '<0' in your if clause? You should also probably make sure there's data in your temp table that meets the rn = 1 criteria, otherwise the selection will return null. If all else fails Sql2005 has try catch blocks, so you can catch the divide by zero exception.

Related

Comparison operator use in Case statement

I am having some trouble with the syntax when I am trying to use the below query in SQL Server. I wanted to show WHERE clause based on condition.
This is my code:
DECLARE #isActual = 0
SELECT
SolverRunId, PointId, TimeStampUtc, Value,
UnitOfMeasure, Reliability
FROM
(SELECT
bt.SolverRunId, bt.PointId, bt.TimeStampUtc,
bt.Value, bt.UnitOfMeasure, bt.Reliability
FROM
cte bt
WHERE
bt.TimeStampUtc = bt.TargetTimeUtc
UNION ALL
SELECT
a.SolverRunId, a.PointId, a.TimeStampUtc, a.Value,
a.UnitOfMeasure, a.Reliability
FROM
cte a
WHERE
-- I tried using this case but it is syntactically incorrect
CASE
WHEN #isActual = 0 THEN a.TimeStamUtc > #endDateUtc
ELSE a.TimestampUtc <= #endDateUtc
END
-- instead of this. I wanted to have conditional where based on #isActual value from 0 to 1
a.TimeStampUtc > #endDateUtc
AND a.SolverRunId = #maxRun) x
ORDER BY
SolverRunId, PointId, TimeStampUtc;
I wanted to have the where condition to be evaluated based on #isActual set to true or false
As mentioned in the comments, don't use a CASE in the WHERE just use proper boolean logic with AND and OR clauses. In your question your variable #isActual is also missing a data type, so I have assumed it is a bit:
DECLARE #isActual bit = 0;
SELECT SolverRunId,
PointId,
TimeStampUtc,
Value,
UnitOfMeasure,
Reliability
FROM (SELECT bt.SolverRunId,
bt.PointId,
bt.TimeStampUtc,
bt.Value,
bt.UnitOfMeasure,
bt.Reliability
FROM cte bt
WHERE bt.TimeStampUtc = bt.TargetTimeUtc
UNION ALL
SELECT a.SolverRunId,
a.PointId,
a.TimeStampUtc,
a.Value,
a.UnitOfMeasure,
a.Reliability
FROM cte a
WHERE a.TimeStampUtc > #endDateUtc
AND a.SolverRunId = #maxRun
AND ((#isActual = 0 AND a.TimeStamUtc > #endDateUtc)
OR (#isActual = 1 AND a.TimestampUtc <= #endDateUtc))) x
ORDER BY SolverRunId,
PointId,
TimeStampUtc;
You may also want experiment with adding RECOMPILE to the OPTION clause of the above, as the query plan requirements for when #isActual has a value of 1 or 0 could be quite different.

Only one expression can be specified in the select list w

I am having problem in part of my code anyway to do this
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS. The update part is working but how to use insert into to calculate if a condition is not meant it will insert.
IF
/* CHECKLIST TO UPDATE*/
(NOT EXISTS
(SELECT *
FROM ENERGY.D_ENERGY_REFERENCE D_ENERGY_REFERENCE
,ENERGY.D_CHECK_LIST D_CHECK_LIST
WHERE D_ENERGY_REFERENCE.ID = D_CHECK_LIST.ID
AND D_ENERGY_REFERENCE.REFERENCE = 19051
)
)
BEGIN
INSERT INTO DB.D_ENERGY_REFERENCE(ID, REFERENCE_NO, REFERENCE,VALUE_INTEGER)
(SELECT ID,
(SELECT ISNULL(MAX(REFERENCE_NO), 0) + 1 FROM DB.D_ENERGY_REFERENCE),
19051, (SELECT D_CHECK_LIST.ID,
CASE
WHEN CAST(COUNT(CASE WHEN D_CHECK_LIST.EVALUATION NOT IN (0,1) THEN EVALUATION ELSE NULL END) AS FLOAT) = 0 THEN NULL
ELSE
(
CAST(COUNT(CASE WHEN D_CHECK_LIST.EVALUATION IN (2, 3, 50001, 50003, 50004, 50005, 50006, 50020, 50027, 50028) THEN EVALUATION ELSE NULL END) AS FLOAT)
/
CAST(COUNT(CASE WHEN D_CHECK_LIST.EVALUATION NOT IN (0,1) THEN EVALUATION ELSE NULL END) AS FLOAT)
) * 100
END FROM DB.D_CHECK_LIST
GROUP BY D_CHECK_LIST.ID)
FROM DB.D_ENERGY_REFERENCE D_ENERGY_REFERENCE
WHERE D_ENERGY_REFERENCE.ID = ID AND D_ENERGY_REFERENCE.REFERENCE = 19051
GROUP BY D_ENERGY_REFERENCE.ID
)
END
Can you please check this following part in the sub query of your script-
.......
19051,
(
SELECT
D_CHECK_LIST.ID, -- This is the column 1
CASE
WHEN -- Here you are generating column 2 in the sub query
......
)
Here you are selecting 2 column - one is "D_CHECK_LIST.ID" and other one is generation through CASE WHEN statement. I think you should SELECT any 1 column from those 2 column. If both are required, you can use separate Sub query for that.
The ERROR code "Only one expression can be specified in the select list when the subquery is not introduced with EXISTS" is self explanatory that you can not implement a Sub Query with more than 1 column selected unless the Sub Query is using inside EXISTS method.

SQL Server IF EXISTS THEN 1 ELSE 2

Using Sql Server 2012. I have a stored procedure and part of it checks if a username is in a table. If it is, return a 1, if not, return a 2. This is my code:
IF EXISTS (SELECT * FROM tblGLUserAccess WHERE GLUserName ='xxxxxxxx') 1 else 2
However, I keep receiving the below error:
Incorrect syntax near '1'.
Is this even possible with an IF EXIST?
Regards,
Michael
If you want to do it this way then this is the syntax you're after;
IF EXISTS (SELECT * FROM tblGLUserAccess WHERE GLUserName ='xxxxxxxx')
BEGIN
SELECT 1
END
ELSE
BEGIN
SELECT 2
END
You don't strictly need the BEGIN..END statements but it's probably best to get into that habit from the beginning.
How about using IIF?
SELECT IIF (EXISTS (SELECT 1 FROM tblGLUserAccess WHERE GLUserName ='xxxxxxxx'), 1, 2)
Also, if using EXISTS to check the the existence of rows, don't use *, just use 1. I believe it has the least cost.
Its best practice to have TOP 1 1 always.
What if I use SELECT 1 -> If condition matches more than one record then your query will fetch all the columns records and returns 1.
What if I use SELECT TOP 1 1 -> If condition matches more than one record also, it will just fetch the existence of any row (with a self 1-valued column) and returns 1.
IF EXISTS (SELECT TOP 1 1 FROM tblGLUserAccess WHERE GLUserName ='xxxxxxxx')
BEGIN
SELECT 1
END
ELSE
BEGIN
SELECT 2
END
In SQL without SELECT you cannot result anything. Instead of IF-ELSE block I prefer to use CASE statement for this
SELECT CASE
WHEN EXISTS (SELECT 1
FROM tblGLUserAccess
WHERE GLUserName = 'xxxxxxxx') THEN 1
ELSE 2
END
You can define a variable #Result to fill your data in it
DECLARE #Result AS INT
IF EXISTS (SELECT * FROM tblGLUserAccess WHERE GLUserName ='xxxxxxxx')
SET #Result = 1
else
SET #Result = 2
What the output that you need, select or print or .. so on.
so use the following code:
IF EXISTS (SELECT * FROM tblGLUserAccess WHERE GLUserName ='xxxxxxxx') select 1 else select 2

Creating alternating 0 and 1 values based on changing data value

I have data in my sql database and need to create changeControl value.
data changeControl
M-0101 0
M-0101 0
M-02 1
M-03 0
OT-014 1
OT-014 1
M-228 0
M-228 0
Are there any way to create this changeControl value depend on data?
There is an easy way, but it depend on your DB.
In SQL Server and in Oracle you can use DENSE_RANK() to get the order of the values, using that you'll have the same number for equal values
In SQL Server
SELECT data
, (DENSE_RANK() OVER (ORDER BY data) - 1) % 2 changeControl
FROM Table1
In Oracle the % is not present, we need to use the function MOD()
SELECT data
, MOD((DENSE_RANK() OVER (ORDER BY data) - 1), 2) changeControl
FROM Table1
In MySQL there is no DENSE_RANK, but there is another way to get the result
SELECT data, changeControl
FROM (SELECT #last
, data
, #id := CASE WHEN #last <> data THEN 1 - #id
WHEN #last = data THEN #id
ELSE 0
END changeControl
, #last:= data
FROM Table1
, (SELECT #id := 0) a) b
if you don't mind additional column you can run directly the subquery.
p.s.: If you don't specify what DB you're using we have to guess.
You need to first break your data into groups, and assign each group a number.
Then join this information with main table, sort by data, and take modulo by two from group number - this will be your changeControl column.
select tn.*, t.RN % 2 as changeControl from tableName tn
left join
(select ROW_NUMBER() OVER (ORDER BY data) AS RN, data
from tableName group by data) t
on t.data = tn.data
order by tn.data
This problem cannot be solved by taking modulo by two of id or data hash - id keeps changing even for identical values, and two sequential hashes although may be different, can both be even, producing same 'changeControl' value.
To implement this, you should handle the RowStyle event of the GridView. Here is some sample code:
using DevExpress.XtraGrid.Views.Grid;
private void gridView1_RowStyle(object sender,
DevExpress.XtraGrid.Views.Grid.RowStyleEventArgs e) {
GridView View = sender as GridView;
if(e.RowHandle >= 0) {
int changeControl = Convert.ToInt32(View.GetRowCellValue(e.RowHandle, View.Columns["changeControl"]));
if(changeControl == 1) {
e.Appearance.BackColor = Color.Salmon;
}
else
e.Appearance.BackColor = Color.SeaShell;
}
}
Please also review this example:
How to change a row style based on a column value
You can create alternatining 0 and 1 values for change control column based on where id is odd or even
SELECT id , data ,
CASE WHEN id%2=0 THEN 1 ELSE 0 END as changecontrol
FROM tableName
EDIT
SELECT id , data ,
CASE WHEN
RIGHT(data,LEN(data) - CHARINDEX('-',data)) % 2 = 0
THEN 1
ELSE 0 END as changecontrol
FROM tableName

How to Short-Circuit SQL Where Clause

I am trying to perform the following query in SQL server:
declare #queryWord as nvarchar(20) = 'asdas'
SELECT * FROM TABLE_1
WHERE (ISDATE(#queryWord) = 1)
AND TABLE_1.INIT_DATE = CONVERT(Date, #queryWord)
This obviously causes an error because 'asdas' cannot be converted to Date. Although, I was expecting a different behavior. That is, because ISDATE(#queryWord) = 1 is false, I was expecting SQL to not check the second condition, but apparently, it does.
I know there are some other ways to perform this query but this is not my question. I wonder if there is some way to do not check the second condition is the first one does not satisfy. I am curious because I thought that SQL already did this.
SQL Server does not do short-circuiting (nor should it).
If you need it to not try something under some circumstances, you need to force that in the way that you write your query.
For this query the easiest fix would be to use a CASE expression in your WHERE clause.
declare #queryWord as nvarchar(20) = 'asdas'
SELECT * FROM TABLE_1
WHERE TABLE_1.INIT_DATE = (CASE WHEN ISDATE(#queryWord) = 1
THEN CONVERT(Date, #queryWord)
ELSE NULL END)
Off-hand, CASE and query-nesting are the only two supported ways that I can think of to force an order of evaluation for dependent conditions in SQL.
I Guess you could do it in 2 passes:
declare #queryWord as nvarchar(20) = 'asdas'
select
*
from
(
SELECT * FROM TABLE_1
WHERE (ISDATE(#queryWord) = 1) ) t1
where t1.INIT_DATE = CONVERT(Date, #queryWord)
So your inner query runs the first test and the outer query the second. In a single query, I don't believe there is any way to force any order of evaluating conditions.
Why not do a CASE in the WHERE condition?
DECLARE #tester TABLE (
theDate DATE,
theValue INT
)
INSERT INTO #tester VALUES ('2013-10-17', 35)
INSERT INTO #tester VALUES ('2013-10-16', 50)
INSERT INTO #tester VALUES ('2013-10-15', 2)
declare #queryWord as nvarchar(20) = 'asdas'
SELECT *
FROM #tester
WHERE theDate =
CASE
WHEN ISDATE(#queryWord) = 1 THEN CONVERT(Date, #queryWord)
ELSE theDate
END
SET #queryWord = '2013-10-17'
SELECT *
FROM #tester
WHERE theDate =
CASE
WHEN ISDATE(#queryWord) = 1 THEN CONVERT(Date, #queryWord)
ELSE theDate
END
It can be "simulated" with a CASE statement. But you have to make the first condition giving a TRUE value to avoid checking of the 2nd condition :
declare #queryWord as nvarchar(20) = 'asdas'
SELECT *
FROM TABLE_1
WHERE (CASE
WHEN ISDATE(#queryWord) = 0 THEN 0
WHEN TABLE_1.INIT_DATE = CONVERT(Date, #queryWord) THEN 1
ELSE 0 END) = 1
There is no defined evaluation order in a SQL statement -- except in the case of case expressions, and even there the order isn't so much defined as the result guaranteed. The conditions in your where clause could theoretically be done in parallel or alternating order.
Case expressions differ not by having a defined order, but by having a guaranteed result. IOW, case when 1=1 then 0 When longrunningfunction() = 1 then 2 end is guaranteed to return zero, but there is no promise not to run the longrunningfunction.