Short circuit WHERE using CASE containing a CONTAINS - sql

I'm attempting to write something like:
SELECT Id FROM SomeTable
WHERE
CASE WHEN (#param IS NULL) THEN
1
ELSE
CONTAINS([FullText],#param)
END = 1
but I can't seem to get SQL Server not to complain about the syntax. Is there a way to use CASE to short-circuit the CONTAINS search?
Even doing something like this doesn't seem to short-circuit:
CASE WHEN (#param IS NULL) THEN
1
ELSE
(CASE WHEN CONTAINS([FullText], #param ) THEN
1
ELSE
0
END)
END = 1

If you look at the execution plan you can see that case is translated to a series of if... else where both part are executed.
It seems like the only way to avoid execution of undesirable part is
if #param is null
select * from myTable
else
select * from myTable
where <expensive condition check>

Just simplify your query :
SELECT Id FROM SomeTable
WHERE #param IS NULL OR CONTAINS([FullText],#param)
So if #param is NULL it will not check for second condition (short circuit)

Since sql server doesn't guarantee short-circuit conditions, you can do something like this to avoid the null predicate error:
SELECT Id
FROM SomeTable
WHERE #param IS NULL
OR CONTAINS([FullText], ISNULL(#param, 'a'))
This way, if #param is null, you will not get an error. I'm not so sure about performance, however - if there is no short-circuit conditions it means that perhaps both parts of the where clause will evaluate and that might take a while.

Related

SQL case-when-else statement efficiency

When I use this statement;
UPDATE TABLE
SET FIELD = CASE
WHEN NAME = 'a' THEN (SELECT NO FROM TABLE_TWO WHERE NAME = 'a')
ELSE 'x' END
WHERE FIELD_TWO = 1
if TABLE.NAME != 'a' will the select SQL executed nevertheless?
Moreover, a little extra question, do you think it is proper to have such logic in SQL code for any given product? I think having any logic in SQL makes its coverage very difficult, and hard to maintain, what do you think?
edit: select statement only returns a single value, ignore the case where it can return multiple values, that case is not in the scope of this question.
The Oracle manual claims that it does short-circuit evaluation:
Oracle Database uses short-circuit evaluation. For a simple CASE expression, the database evaluates each comparison_expr value only before comparing it to expr, rather than evaluating all comparison_expr values before comparing any of them with expr
In your case comparison_expr is the WHEN NAME = 'a' part and if the manual is right, the database will not run the select if name has a different value.
I think it would be easier to read and maintain, when you split it into two UPDATE-statements like this:
UPDATE TABLE SET FIELD = (SELECT TOP 1 NO FROM TABLE_TWO WHERE NAME = 'a')
WHERE FIELD_TWO = 1
AND NAME='a'
UPDATE TABLE SET FIELD = 'x'
WHERE FIELD_TWO = 1
AND NAME != 'a'
It lets you add more cases easily and you can generalize the cases if there are more of them, like:
UPDATE TABLE SET FIELD = (SELECT TOP 1 NO FROM TABLE_TWO WHERE NAME = TABLE.FIELD)
WHERE FIELD_TWO = 1
AND NAME IN ('a','b','c')
If I were you, I would use a variable so that case doesn't compute a scalar value everytime. Something like following:
DECLARE #myVar VARCHAR(10);
SELECT TOP 1 #myVar = NO FROM TABLE_TWO WHERE NAME = 'a';
UPDATE TABLE
SET FIELD = CASE
WHEN NAME = 'a' THEN #myVar
ELSE 'x' END
WHERE FIELD_TWO = 1

Ignore other results if a resultset has been found

To start, take this snippet as an example:
SELECT *
FROM StatsVehicle
WHERE ((ReferenceMakeId = #referenceMakeId)
OR #referenceMakeId IS NULL)
This will fetch and filter the records if the variable #referenceMakeId is not null, and if it is null, will fetch all the records. In other words, it is taking the first one into consideration if #referenceMakeId is not null.
I would like to add a further restriction to this, how can I achieve this?
For instance
(ReferenceModelId = #referenceModeleId) OR
(
(ReferenceMakeId = #referenceMakeId) OR
(#referenceMakeId IS NULL)
)
If #referenceModelId is not null, it will only need to filter by ReferenceModelId, and ignore the other statements inside it. If I actually do this as such, it returns all the records. Is there anything that can be done to achieve such a thing?
Maybe something like this?
SELECT * FROM StatsVehicle WHERE
(
-- Removed the following, as it's not clear if this is beneficial
-- (#referenceModeleId IS NOT NULL) AND
(ReferenceModelId = #referenceModeleId)
) OR
(#referenceModeleId IS NULL AND
(
(ReferenceMakeId = #referenceMakeId) OR
(#referenceMakeId IS NULL)
)
)
This should do the trick.
SELECT * FROM StatsVehicle
WHERE ReferenceModelId = #referenceModeleId OR
(
#referenceModeleId IS NULL AND
(
#referenceMakeId IS NULL OR
ReferenceMakeId = #referenceMakeId
)
)
However, you should note that this types of queries (known as catch-all queries) tend to be less efficient then writing a single query for every case.
This is due to the fact that SQL Server will cache the first query plan that might not be optimal for other parameters.
You might want to consider using the OPTION (RECOMPILE) query hint, or braking down the stored procedure to pieces that will each handle the specific conditions (i.e one select for null variables, one select for non-null).
For more information, read this article.
If #referenceModelId is not null, it will only need to filter by
ReferenceModelId, and ignore the other statements inside it. If I
actually do this as such, it returns all the records. Is there
anything that can be done to achieve such a thing?
You can think of using a CASE for good short circuit mechanism
WHERE
CASE
WHEN #referenceModelId is not null AND ReferenceModelId = #referenceModeleId THEN 1
WHEN #referenceMakeId is not null AND ReferenceMakeId = #referenceMakeId THEN 1
WHEN #referenceModelId is null AND #referenceMakeId is null THEN 1
ELSE 0
END = 1

CASE WHERE change operator - TSQL

I was wondering if you could do a case statement in the WHERE clause which changes the operator.
What I am trying to do is filter out results based on a boolean value.
SELECT *
FROM table1
WHERE
CASE #Status
WHEN 1 THEN Name LIKE 'SOMETHING%'
WHEN 2 THEN Name NOT LIKE 'SOMETHING%'
END
I was wondering if this is possible?
You can do it without a SWITCH expression, like this:
SELECT *
FROM table1
WHERE
(#Status = 1 AND Name LIKE 'SOMETHING%')
OR (#Status = 2 AND Name NOT LIKE 'SOMETHING%')
Since #Status can be equal to only one thing at a time, only one component of the OR expression would determine the outcome of the WHERE condition.

Issue with 'NOT IN' statement in SQL

Can anyone please point out what is wrong with the following SQL statement:
SELECT DiaryType
FROM tblDiaryTypes
WHERE DiaryType NOT IN (SELECT NextDiary
FROM tblActionLinks
WHERE HistoryType = 'Info Chased');
Now the nested SELECT statement currently returns NULL because there are initially no entries in tblActionLinks, and I am wondering if that is the issue.
The outer SELECT statement if executed on its own does return all the Diary Types from tblDiaryTypes as expected. But when I add the nested SELECT statement to exclusde certain values, then the overall SQL statement returns empty!
Does this have something to do withthe fact that tblActionLinks is currently empty? If so, how can I amend my SQL statement to handle that possibility.
For SQL SERVER (you didn't specified sql engine) try with:
SELECT ISNULL(NextDiary, 0) ...
When no rows found all value is null then it will return 0
Are you sure there are no entries currently in tblActionLinks? If there are no entries in tblActionLinks, then outer query should return all records
Does this have something to do withthe fact that tblActionLinks is currently empty?
Yes... NULL doesn't being handled so good in SQL, Comparing a value to NULL is undifned try give for null a flag value like -999:
SELECT DiaryType
FROM tblDiaryTypes
WHERE DiaryType NOT IN (SELECT NVL(NextDiary, -999) -- <===
FROM tblActionLinks
WHERE HistoryType = 'Info Chased');
NVL(NextDiary, -999) means that if NextDiary IS NULL, replace the value with -999
docs
I would rewrite your query the following way:
SELECT DiaryType
FROM tblDiaryTypes
WHERE NOT EXISTS (SELECT NextDiary
FROM tblActionLinks
WHERE HistoryType = 'Info Chased'
AND NextDiary = DiaryType)
This ensures proper behaviour irrespective of ANSI_NULLS setting and you don't have to worry about properly choosing the magic value returned by ISNULL(NextDiary, 0) (what if you have DiaryType equal to 0 in tblDiaryTypes?)

SQL Server CASE inside where clause not working for NULL value

What I want is when #AddressCode is null then simply execute
SELECT
*
FROM
Addresses
WHERE
1= 1
If #AddressCode is not NULL then execute
SELECT
*
FROM
Addresses
WHERE
1= 1 AND #AddressCode=Addresses.AddressCode
How can I achieve this ... I tried query below and its not working for Address with NULL value or when #AddressCode is NULL .... (Addresses is just an imaginary table name)
Declare #AddressCode varchar(20)
Set #AddressCode=NULL
SELECT
*
FROM
Addresses
WHERE
1= 1
AND
CASE #AddressCode
WHEN NULL
THEN Addresses.AddressCode
ELSE #AddressCode
END =Addresses.AddressCode
I tried hard but am not able to make it ... I know i can write IF statement and write individual select statement but don't want to do that as i have lots of such criteria as #AddressCode :(
SELECT *
FROM
Addresses
WHERE
(#AddressCode IS NULL OR Addresses.AddressCode = #AddressCode)
You are using CASE improperly in this case (comparing with null). The right way is
CASE
WHEN #AddressCode IS NULL THEN Addresses.AddressCode
ELSE #AddressCode
END
The problem with your query is that CASE a WHEN b THEN c... tests if a=b which turns into FALSE(NULL) if one of the operands is null.
Another question is if you really need CASE here...