How to Short-Circuit SQL Where Clause - sql

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.

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

Query returning null values but there is no error

I am picking values from a column TRANSAMT from the table GLPJD. The value can be negative in the table but when it's negative, I have to make it positive and when its positive, I leave it like that. The result being returned in NULL, what is left in the query. It is a subquery
declare #FY int
set #FY = 2016
SELECT
DbName, District,
(SELECT
CASE
WHEN (TRANSAMT < 0)
THEN ISNULL(SUM(TRANSAMT) * -1, 0)
ELSE ISNULL(SUM(TRANSAMT), 0)
END
FROM
GLPJD
LEFT JOIN
GLAMF ON GLPJD.ACCTID = GLAMF.ACCTID
WHERE
GLPJD.AUDTORG = D.DbName
AND GLPJD.ACCTID = 110002
AND GLPJD.FISCALYR < #FY
GROUP BY
TRANSAMT) AS TRANSAMT
FROM
Districts D
Results of the query:
Your error comes probably from the fact that performing a SUM while having NULL values returns a NULL result. So you should use the ISNULL part inside SUM, and not the other way around.
Also you may consider using the function ABS, so you could get rid of the CASE part. My approach would be changing this part from your current code:
SELECT
CASE
WHEN (TRANSAMT < 0)
THEN ISNULL(SUM(TRANSAMT) * -1, 0)
ELSE ISNULL(SUM(TRANSAMT), 0)
END
To just:
SELECT SUM(ABS(ISNULL(TRANSAMT, 0)))
sum function omits null values
look:
http://sqlfiddle.com/#!4/0a806/1

Using CASE within a where clause - with a parameter

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))
);

set variable to NULL if nested query returns no results

Suppose you have the following query. If the nested query returns NULL(0 results), the stored procedure crashes with the following error mentioned below. I found out I can re-write the code in the Alternative query below, but I'm wanting to find an easier syntax to write it. I have about 10 of these, and some have multiple nested queries. Is there an easier way to write them? I'm not an expert in SQL, so I'm always looking for suggestions! Thanks.
Query (that sometimes crashes):
SET #sampleid = (
SELECT
[sampleid]
FROM [sample]
WHERE [identifyingnumber] = #sample_identifyingnumber
Error from query:
Subquery returned more than 1 value. This is not permitted when the subquery
follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Alternative query that never crashes:
IF
(
SELECT
COUNT([sampleid])
FROM [sample]
WHERE [identifyingnumber] = #sample_identifyingnumber
) = 0
BEGIN
SET #sampleid = NULL
END
ELSE
BEGIN
SET #sampleid =
(
SELECT
DISTINCT [sampleid]
FROM [sample]
WHERE [identifyingnumber] = #sample_identifyingnumber
)
END
===============
Example that's more complex:
SET #testcodeid = (
SELECT
[testcodeid]
FROM [testcode]
WHERE [testcode].[name] = (
SELECT [test_code]
FROM [ws_test_request]
WHERE [client_id] = #clientid
AND [sample_specimen_id] = #sample_identifyingnumber
)
);
try
SELECT #sampleid = [sampleid]
FROM [sample]
WHERE [identifyingnumber] = #sample_identifyingnumber
You set to NULL the records for which do not exist records in sample_table with identifyingnumber equal to sample_identifyingnumber:
UPDATE my_table
SET sampleid = NULL
WHERE NOT EXISTS
(SELECT 'X'
FROM sample_table
WHERE identifyingnumber = sample_identifyingnumber);
You set to MIN (DISTINCT sampleid) the field sampleid for which exist in sample_table records where identifying_number = sample_identifyingnumber:
UPDATE my_table
SET sampleid =
(SELECT MIN (DISTINCT sampleid)
FROM sample_table
WHERE identifyingnumber = sample_identifyingnumber)
WHERE EXISTS
(SELECT 'X'
FROM sample_table
WHERE identifying_number = sample_identifyingnumber);
I have written MIN (DISTINCT ...) in case we find more sampleids corresponding to the same identifyingnumber: in this unlucky case, I take the MINIMUM of the X (with X strictly > 1) different sampleids.
I need two updates instead of one.