Using CASE within a where clause - with a parameter - sql

I am looking for a good example of a case statement inside a where clause, using a variable. The number returned by the variable determines the result of the where clause. My query looks like this:
IF #RequestType = 99
SELECT #Received = count(*)
FROM Request r
WHERE request_received_date between #beginDate and #endDate
AND request_type_id <> 5
ELSE IF #RequestType = 100
BEGIN
SELECT #Received = count(*)
FROM Request r
WHERE request_received_date between #beginDate and #endDate
END
ELSE
BEGIN
SELECT #Received = count(*)
FROM Request r
WHERE request_received_date between #beginDate and #endDate
AND request_type_id =
(CASE WHEN #RequestType = 1 THEN (1)
WHEN #RequestType = 2 THEN (2)
WHEN #RequestType = 3 THEN (3)
WHEN #RequestType = 4 THEN (4)
WHEN #RequestType = 5 THEN (5)
END)
END
So as you can see, there are 7 options - the customer wants to be flexible with the amount of data they see in the report. This looks cumbersome to me but I can't think of any other way to do this.

you could probably simplify the query to...
SELECT
count(*) as Received
FROM
( select 3 as RqType,
'2015-10-01' as beginDate,
'2015-10-20' as endDate) as PV,
Request r
WHERE
request_received_date between PV.beginDate and PV.endDate
AND case when PV.RqType = 100 then 1=1
when PV.RqType = 99 then r.request_type_id <> 5
when PV.RqType <= 6 then PV.RqType = r.request_type_id
end
The LAST case/when component matches on the request type is getting the values under 6 to match that of the request type. If you have negatives, then just adjust that accordingly.
I revised the query to have a prequery "PV" indicating the parameter values you are looking for, so they can just be changed to whatever request type and date-range as you need. This should be good for SQL-Server, and probably other SQL engines too. By just doing a query of hard values with no "FROM", it essentially creates a result set of a single record with those values of which you can then use for the rest of the query. Any by being a single record, no issue for any Cartesian joins.

There is usually no need for CASE WHEN in WHERE clauses, as you have AND and OR. Your queries translate to:
select #Received = count(*)
from request
where request_received_date between #beginDate and #endDate
and
(
#RequestType = 100
or
(#RequestType = 99 and request_type_id <> 5)
or
(#RequestType = request_type_id and request_type_id in (1,2,3,4,5))
);

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.

IF equals or clause

I have a stored procedure that runs two separate queries and puts the data into two temporary tables. I then have an IF statement below that, depending on the outcome will display one of the two tables.
DECLARE #DATASET1 AS FLOAT
DECLARE #DATASET2 AS FLOAT
SET #DATASET1 = (SELECT SUM(PREM) FROM #Prem1)
SET #DATASET2 = (SELECT SUM(PREM) FROM #Prem2)
IF (#DATASET1 = 0)
BEGIN
SELECT DATE,
SUM(PREM) AS PREM
FROM #DATASET2
GROUP BY YEAR, MONTH, DATE
ORDER BY YEAR, MONTH
END
IF (#DATASET2 = 0)
BEGIN
SELECT DATE,
SUM(PREM) AS PREM
FROM #DATASET1
GROUP BY YEAR, MONTH, DATE
ORDER BY YEAR, MONTH
END
This was working well until I hit some output on dataset1 that didn't produce 0 but just produced no data at all.
So I was wondering if it is possible to update this part of the query to almost say:
IF (#DATASET1 = 0 or '')
I have tried something like that but doesn't seem to work, hence my question.
Your scalar aggregate query (SELECT SUM(PREM) FROM #Prem1) will return a NULL value if there is no record in the table or of column PREM contains only NULL values.
You can handle NULL with IS NULL, like so:
IF (#DATASET1 IS NULL OR #DATASET1 = 0)
You can also use COALESCE():
IF (COALESCE(#DATASET1, 0) = 0)
You can achieve it by using ISNULL()
IF (ISNULL(#DATASET1, 0) = 0)
Syntax: ISNULL(expression, value)
Parameter Description
expression Required. The expression to test whether is NULL
value Required. The value to return if expression is NULL

Condition for a SQL query to check if dates are between prior period and current month and execute subqueries based on given gate

DECLARE #Todaydate DATE
SET #Todaydate = '12/31/2017'
SELECT
CASE
WHEN DATEDIFF(dd,#Todaydate,getdate()) >= 31
THEN (SELECT a.CU, , b.abc
FROM histhold a, security b
WHERE T_QUANTITY_P <> 0
AND ACCOUNTING_DATE = '04/30/2018'
AND a.cu = b.CU)
ELSE ''
END
Just do the logic in the WHERE clause:
SELECT a.CU, , b.abc
FROM histhold a
INNER JOIN security b
ON a.cu = b.CU
WHERE T_QUANTITY_P <> 0
AND ACCOUNTING_DATE = '04/30/2018'
AND DATEDIFF(dd,#Todaydate,getdate()) >= 31;
Unless this is part of some larger procedure/script I can't imagine a reason why you would only want to execute a query given certain conditions (as opposed to executing the query and restricting the results based on those conditions potentially returning an empty recordset).

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.

How to solve Divide by Zero exception in SQL stored procedure?

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.