Sql short circuit OR or conditional exists in where clause - sql

I'm trying to force sql server to make a short circuit OR comparison on some fields. On the left side of the Or I have a simple variable comparison and on the right side I have a pretty 'heavy' subquery.
WHERE
(#Var = 'DefaultValue' ) OR
Exists(select * from atable)
Is there any way to only execute the right side of the or statement if the first statement is false.
I've tried case and if else statements but can't find any working syntax.
I'm using a MS SQL server

You can't do what you want in a single SQL statement. You can do something like this however in a stored proc
If #Var = 'DefaultValue' then
BEGIN
SELECT * FROM table
END
ELSE
BEGIN
SELECT * FROM table
WHERE Exists(select * from atable)
END
If you've got a lot of inputs you should consider Dynamic SQL

How about this?
WHERE 1 = CASE #Var
WHEN 'DefaultValue' THEN 1
ELSE (SELECT TOP 1 1 FROM atable)
END

Related

Parameterized WHERE clause in SQL Server Stored Procedure

I have a Stored Procedure to get the details of Invoices
Some occasions I get the list of invoices by sending only the InvoiceID
But in some other occasions I need to get the list of invoices as per the search fields supplied by the user. To do this I send all the fields to the Stored Procedure and use those parameters as below. I included only 2 columns but there are more.
SELECT * FROM INVOICES I
WHERE
(#InvoiceNumber is null or I.InvoiceNumber = #InvoiceNumber)
and
(#PONo is null or I.PONo = #PONo)
Is there a way to send the condition for the WHERE clause as one parameter?
Yes, it is possible with Dynamic SQL, but I highly discourage to do that.
SELECT * FROM tbl WHERE #condition:
If you are considering to write the procedure
CREATE PROCEDURE search_sp #condition varchar(8000) AS
SELECT * FROM tbl WHERE #condition
Just forget it. If you are doing this, you have not completed the transition to use stored procedure and you are still assembling your
SQL code in the client.
It will also open your application to SQL Injection attacks.
You can use custom type to pass table as parameter https://msdn.microsoft.com/pl-pl/library/bb510489(v=sql.110).aspx or you can use default parameters
If you're using SQL Server 2016 or similar (check by calling select compatibility_level, name from sys.databases and seeing that your DB is 130 or higher) then you can use the string_split builtin function.
I found it works best like this (spread out for clarity)
CREATE PROCEDURE [dbo].[GetInvoices]
#InvoiceNumber int = NULL
#PONo nvarchar(1024) = NULL
AS
SELECT * from [Invoices] AS [i]
WHERE
i.InvoiceNumber = ISNULL(#InvoiceNunber, i.InvoiceNunber)
AND CASE
WHEN #PONo is null
THEN 1
ELSE (CASE
WHEN i.PONo IN (select value from string_split(#PONo, ','))
THEN 1
ELSE 0
END)
END
= 1
So if you pass in a null to either parameter it gets translated as where x = x which is always true, and if you pass in a CSV value, it selects it from a split table of values that, if present, results in the where clause being where 1=1, which is true or 0=1 if the value is not present in the input list.
So here you can pass in an invoice number, or PO number, or both, or neither and it should return what you expect.

SQL IF EXISTS with OR Condition

I am facing a performance issue with a SQL statement. I have noticed a performance degradation in one SQL statement in one of a procedure.
SQL Statement:
IF EXISTS(SELECT TOP 1 FROM TABLE1 WHERE COLUMN1 = 'XYZ') OR #ISALLOWED = 1
BEGIN
-- SQL Statements
END
I couldn't able to understand why OR statement with IF Exists statement is causing performance issue in above query. Because if I rewrite the above statement like this:
DECLARE #ISVALUEEXISTS BIT
SET #ISVALUEEXISTS = 0
IF EXISTS(SELECT TOP 1 FROM TABLE1 WHERE COLUMN1 = 'XYZ')
SET #ISVALUEEXISTS = 1
IF (#ISVALUEEXISTS = 1 OR #ISALLOWED = 1 )
BEGIN
--SQL Statements
END
Then performance issue is gone. So, I'm couldn't able to understand how and why OR condition with IF Exists statement is causing problem.
Anyone has any idea about this?
If you have this query inside stored procedure this could happen because of parameter sniffing.
Try something like this to check it:
declare #ISALLOWED_internal
select #ISALLOWED_internal = #ISALLOWED
IF EXISTS(SELECT TOP 1 FROM TABLE1 WHERE COLUMN1 = 'XYZ') OR #ISALLOWED_internal = 1
BEGIN
-- SQL Statements
END

How to use If/Else or Case statement for a checkbox with in a Select Query in SQL

I have a check box on front end.
If the check box is checked: data with only checked chk1 should appear on front end.
If the check box is not checked: full data should appear on front end.
Please suggest how should I proceed with the same in SQL Using If else / Case statement.
I am using:
SELECT *
FROM table1
where (some conditions) AND
CASE #a.Id_Oem_Irm
WHEN 0 THEN (a.id_oem_irm in(0,1))
WHEN 1 THEN (a.id_oem_irm in (1))
END
PS: a.Id_Oem_Irm: a is the table name, Id_oem_irm is the column name for check box.
I would recommend writing this as:
where (some conditions) AND
((#a.Id_Oem_Irm = 0 and a.id_oem_irm in(0, 1)) OR
(#a.Id_Oem_Irm = 1 and a.id_oem_irm in (1) )
)
I am not sure what #a.Id_Oem_Irm is supposed to be. I suspect you want a variable there.
Or you could tune it a bit like this:
SELECT *
FROM table1
where (some conditions) AND
(a.id_oem_irm = 1 OR
#a.Id_Oem_Irm = 0)
This is only valid if #a.Id_Oem_Irm is always 0 or 1. Otherwise, you obviously should add a #a.Id_Oem_Irm IN (0,1) or <= 1 (if it can't be negative) condition.
What is a? The alias for another table you didn't include here?
The simpler way to do this is create a storedprocedure which will take the input "#a.Id_Oem_Irm"
Example:
CREATE PROCEDURE p_get_a_data(
IN Id INT(1)
)
BEGIN
IF (Id = 0) BEGIN
--YOur query to retrieve partiular row/ column
END;
IF(Id = 1) BEGIN
--YOur query to retrieve all data
SELECT * FROM table1
where (some conditions)
END;
END
The usage of stored procedure will give you selected data and the query is precompiled in the database so it faster.
Note: I didn't quite understand what your query is returning so replace mine with yours

T-SQL: WHERE Clause - How to adjust based on input variable

Using SQL Server 2008. I have an input param in my stored proc called '#State'. The param can basically be '--All--' or can contain the state to filter.
So, if it is '--All--' I don't want to incorporate the #State into the where clause. Otherwise I'd like it to filter based on the provided #State. So basically it could result in this....
SELECT * FROM MyTable
WHERE Type='AAA' AND Status=#Status
or, if they pass '--All--'
SELECT * FROM MyTable
WHERE Type='AAA'
How can I do this in a stored proc?
SELECT
*
FROM
MyTable
WHERE
Type='AAA'
AND Status = CASE #Status WHEN '--All--' THEN Status ELSE #Status END
I thought you made a typo. It should be #State, not #Status. This simple query might not be what you are looking for since you want to two sql statements in your requirement.
SELECT * FROM MyTable
WHERE Type='AAA' AND (#State='--All--' or State=#State)
Or...
you could make it even simpler than that:
SELECT * FROM MyTable
WHERE Type='AAA' AND #State in ('--All--', State)
No need to do it in a stored procedure, unless absolutely necessary (or if business/coding practice requires you to do so).

Proper way to handle 'optional' where clause filters in SQL?

Let's say you have a stored procedure, and it takes an optional parameter. You want to use this optional parameter in the SQL query. Typically this is how I've seen it done:
SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND (#MyOptionalParam IS NULL OR t1.MyField = #MyOptionalParam)
This seems to work well, however it causes a high amount of logical reads if you run the query with STATISTICS IO ON. I've also tried the following variant:
SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND t1.MyField = CASE WHEN #MyOptionalParam IS NULL THEN t1.MyField ELSE #MyOptionalParam END
And it yields the same number of high reads. If we convert the SQL to a string, then call sp_ExecuteSQL on it, the reads are almost nil:
DECLARE #sql nvarchar(max)
SELECT #sql = 'SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = ''test'''
IF #MyOptionalParam IS NOT NULL
BEGIN
SELECT #sql = #sql + ' AND t1.MyField = #MyOptionalParam '
END
EXECUTE sp_ExecuteSQL #sql, N'#MyOptionalParam', #MyOptionalParam
Am I crazy? Why are optional where clauses so hard to get right?
Update: I'm basically asking if there's a way to keep the standard syntax inside of a stored procedure and get low logical reads, like the sp_ExecuteSql method does. It seems completely crazy to me to build up a string... not to mention it makes it harder to maintain, debug, visualize..
If we convert the SQL to a string, then call sp_ExecuteSQL on it, the reads are almost nil...
Because your query is no longer evaluating an OR, which as you can see kills sargability
The query plan is cached when using sp_executesql; SQL Server doesn't have to do a hard parse...
Excellent resource: The Curse & Blessing of Dynamic SQL
As long as you are using parameterized queries, you should safe from SQL Injection attacks.
This is another variation on the optional parameter technique:
SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND t1.MyField = COALESCE(#MyOptionalParam, t1.MyField)
I'm pretty sure it will have the same performance problem though. If performance is #1 then you'll probably be stuck with forking logic and near duplicate queries or building strings which is equally painful in TSQL.
You're using "OR" clause (implicitly and explicitly) on the first two SQL statements. Last one is an "AND" criteria. "OR" is always more expensive than "AND" criteria. No you're not crazy, should be expected.
EDIT: Adding link to similar question/answer with context as to why the union / if...else approach works better than OR logic (FYI, Remus, the answerer in this link, used to work on the SQL Server team developing service broker and other technologies)
Change from using the "or" syntax to a union approach, you'll see 2 seeks that should keep your logical read count as low as possible:
SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND #MyOptionalParam IS NULL
union all
SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND t1.MyField = #MyOptionalParam
If you want to de-duplicate the results, use a "union" instead of "union all".
EDIT: Demo showing that the optimizer is smart enough to rule out scan with a null variable value in UNION:
if object_id('tempdb..#data') > 0
drop table #data
go
-- Put in some data
select top 1000000
cast(a.name as varchar(100)) as thisField, cast(newid() as varchar(50)) as myField
into #data
from sys.columns a
cross join sys.columns b
cross join sys.columns c;
go
-- Shwo count
select count(*) from #data;
go
-- Index on thisField
create clustered index ixc__blah__temp on #data (thisField);
go
set statistics io on;
go
-- Query with a null parameter value
declare #MyOptionalParam varchar(50);
select *
from #data d
where d.thisField = 'test'
and #MyOptionalParam is null;
go
-- Union query
declare #MyOptionalParam varchar(50);
select *
from #data d
where d.thisField = 'test'
and #MyOptionalParam is null
union all
select *
from #data d
where d.thisField = 'test'
and d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E';
go
-- Union query with value
declare #MyOptionalParam varchar(50);
select #MyOptionalParam = '5D25E9F8-EA23-47EE-A954-9D290908EE3E'
select *
from #data d
where d.thisField = 'test'
and #MyOptionalParam is null
union all
select *
from #data d
where d.thisField = 'test'
and d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E';
go
if object_id('tempdb..#data') > 0
drop table #data
go
Change from using the "or" syntax to a two query approach, you'll see 2 different plans that should keep your logical read count as low as possible:
IF #MyOptionalParam is null
BEGIN
SELECT *
FROM dbo.MyTableName t1
END
ELSE
BEGIN
SELECT *
FROM dbo.MyTableName t1
WHERE t1.MyField = #MyOptionalParam
END
You need to fight your programmer's urge to reduce duplication here. Realize you are asking for two fundamentally different execution plans and require two queries to produce two plans.