Dynamic where clause with set number of columns - sql

I am bringing in arguments from an arbitrary source and there may be 1,2 or 3 args passed that need to go as a where clause in an sql query.
How can I write this query so no matter what args are passed the query will run with as many args as it can. I was thinking:
Passed in: x=1,y=null,z=5
//Do some simple checks and assign local variables when the value are not null.
if(arg == null)
{
arg = (/*some wild card that allows any value to be returned*/)
}
else{
arg = arg
}
Generated clause: WHERE X=localx AND Y=(ANY VALUE) AND Z=localz
What can I use for Y in this example so I can avoid dynamically creating a string with a variable amount of args? I found myself having a really hard time trying to articulate this problem.

I'm going to assume SQL Server. Use the ISNULL command and make all incoming parameters null when not provided.
SELECT *
FROM Table
WHERE ISNULL(#Param1,Field1)=Field1
AND ISNULL(#Param2,Field2)=Field2
AND ISNULL(#Param3,Field3)=Field3
AND ISNULL(#Param4,Field4)=Field4
AND ISNULL(#Param5,Field5)=Field5

A better solution, if any of the fields might have NULL values is:
SELECT *
FROM Table
WHERE (#Param1 is null or #Param1 = Field1) and
(#Param2 is null or #Param2 = Field2) and
(#Param3 is null or #Param3 = Field3) and
(#Param4 is null or #Param4 = Field4) and
(#Param5 is null or #Param5 = Field5)
The main difference between this and the solution that uses isnull is that this version handles NULL values in the fields. The expression isnull(#param1, field1) = field1 returns false when #param1 is NULL and field1 is NULL. The expression #Param1 is null or #Param1 = Field1 returns true when both are NULL.

Related

Is there a way to use in or = with a case statement in the where clause in sql?

I have a stored procedure that may or may not get a string list of int ids. When it doesn't get it the value is: ' '. Other wise its something like this: '500,507,908'
I'm trying to use it like this:
select ID as projectTeamId, Employee_ID, Supervisor_ID
from ProjectTeam
where Project_ID = #projectId and IsDeleted = 0 and
ID in (CASE #stringList WHEN '' THEN ID ELSE (SELECT * from TurnListStringIntoTable(#stringList)) END)
to get a result set but it errors out with this code when the string list comes in blank:
An error has occurred while processing Report 'MassReleaseHoursReport':
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I know its an issue where id needs to = id instead of being in id. Any ideas on how I can have the case statement work with #stringList = '' then id = id else id in (SELECT * from TurnListStringIntoTable(#stringList))?
TurnListStringIntoTable returns a table from a string list which in my case is just the project Team ID
I would recommend boolean logic rather than a case expression:
where
Project_ID = #projectId
and IsDeleted = 0
and (
#stringList = ''
or id in (select * from TurnListStringIntoTable(#stringList))
)
Unrelated side note: if you are running SQL Server, as I suspect, and your version is 2016 or higher, you can use built-in function string_split() instead of your customer splitter.
Sure!
All you have to do is use the parameterless flavor of case:
select *
from my_table t
where t.col_1 = case
when #var in (1,2,3) then "foo"
when #var = 4 then "bar"
when #var in (5,6,7) then "baz"
else "bat"
end
One might note that the when expressions are not limited to looking at the same variable in any way. The only requirement is that they have to be a boolean expression. They're evaluated in order top to bottom. The first when clause that evaluates to true wins and it's then value is returned.
If there's no else and evaluation fails to find a match, the result value is null.
Your problem though, is that case expressions
return a single value, and
that value must be of the same type. Can have it returning a string in some cases and a table variable in another.
So... your where clause should look something like this:
where ...
and 'true' = case
when #stringList = '' then 'true'
when ID in ( select *
from TurnListStringIntoTable(#stringList)
) then 'true'
else 'false'
end
You'll probably find, too, that invoking a user-defined function to convert a comma-delimited string into a table variable within the where clause is probably a Bad Idea™ due to the performance impact that that will have.
You'd be better off to move your TurnListStringIntoTable call outside of the select statement, thus:
declare #list = TurnListStringIntoTable(#stringlist)
select ...
from ProjectTeam pt
where . . .
and #stringlist = ''
OR exists ( select * from #list x where x.ID = pt.ID )

SQL Server WHERE clause : column IS NULL or column = parameter value

The code snippet below is what I'm trying to achieve, but I'm having trouble making it work. If the parameter that gets passed into the procedure is null, I want to only return the rows with a WHERE clause IS NULL, but if there is a value, I want to return the rows that are equal to the value passed in. Dynamic SQL seems like it would work, but I'm curious if there's an easier way I'm missing. Thanks in advance.
PARAM:
#id varchar(10) = '123456789'
SELECT *
FROM TABLE T
WHERE
CASE
WHEN #id IS NULL THEN (id IS NULL)
ELSE id = #id
END
The logic you want is:
WHERE (#id IS NULL AND id IS NULL) OR
id = #id
You're trying to use a CASE expression like a Case (Switch) statement. Switches don't exist in T-SQL, and a CASE expression returns a scalar value not a boolean result.
Don't, however, use CASE expressions in the WHERE, use proper Boolean logic:
SELECT *
FROM YourTable YT
WHERE (ID = #ID
OR (ID IS NULL AND #ID IS NULL))

Sql Server 2012 Stored procedure query with where clause is null or input parameter

I want to create a stored procedure that uses a an input parameter in the where clause. The parameter will either be text or null.
I.e. suppose I have a table [tbl]
Status val
NULL 158
Acc 155
Acc 152
Null 24
Rej 34
In SQL server the null values can be found by using
WHERE Status is NULL
I also want the stored procedure to be able to handle a case when the parameter equals a specific value.
WHERE Status = #parameter
How do I combine both of these cases? I want something like this.
CREATE PROC test #parameter VARCHAR(10) = ''
SELECT *
FROM [tbl]
IF (#parameter is NULL OR #parameter = '') THEN WHERE STATUS is Null
ELSE WHERE STATUS = #parameter
WHERE (ISNULL(#parameter,'') = '' AND Status IS NULL)
OR Status = #parameter
The top half can only ever evaluate to true if #Status isn't NULL. Otherwise it falls back to the bottom half.
ISNULL(#Status,'') = '' is just the same as saying #Status IS NULL OR #Status = '', and if that's the case then it matches rows where Status IS NULL.
Looking at your pseudocode, you were pretty much there (things I added are in bold):
IF( (#parameter is NULL OR #parameter = '')THEN WHEREANDSTATUS is Null)
ELSE WHERE OR STATUS = #parameter
Just realised this might be better:
WHERE ISNULL(#parameter, '') = ISNULL(Status, '')
It will work the same, but I'm not sure if the ISNULL(Status, '') will stop indexes from being used, so it'd be worth checking the execution plan.
Just include both the condition in your WHERE clause such that it will fetch rows where status having null's or status having specific value
WHERE Status is NULL
OR Status = #parameter

sql server: MERGE has unexpected results

The way these rows usually come into the target table the first time are with a sparse number of columns populated with mostly text data with the remainder of the columns set to NULL. On subsequent passes, the fresh data populates existing known (non null) and unknown (NULL) data. I've ascertained that the fresh data ( #pld) do indeed contain different data. The data does not appear to change. Here's what I have:
BEGIN TRANSACTION
BEGIN TRY
MERGE INTO [metro].listings AS metroList
USING #pld as listnew
ON metroList.id = listnew.id
AND metroList.sid = listnew.sid
WHEN MATCHED AND (
metroList.User != listnew.User
or metroList.Email != listnew.Email
or metroList.LocName != listnew.LocName
) THEN
UPDATE SET
metroList.User = listnew.User,
metroList.Email = listnew.Email,
metroList.LocName = listnew.LocName,
WHEN NOT MATCHED THEN
INSERT
( User,
Email,
LocName
)
VALUES
(
listnew.User,
listnew.Email,
listnew.LocName
);
COMMIT TRANSACTION
END TRY
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH
I've tried replacing the != to under the update portion of the statement with <> . Same results. This has to be related to a comparison of a possible (likely) null value against a string--maybe even another null? Anyway, I'm calling on all sql-geeks to untangle this.
Also you can use option with NULLIF() function.
NULLIF returns the first expression if the two expressions are not equal. If the expressions are equal, NULLIF returns a null value of the type of the first expression.
WHEN MATCHED AND (
NULLIF(ISNULL(metroList.[User],''), listnew.[User]) IS NOT NULL
OR NULLIF(ISNULL(metroList.Email, ''), listnew.Email) IS NOT NULL
OR NULLIF(ISNULL(metroList.LocName, ''), listnew.LocName) IS NOT NULL
)
THEN
Comparing NULL with an empty string will not work.
If either side could be NULL, you could do something like:
WHEN MATCHED AND (
COALESCE(metroList.User, '') <> COALESCE(listnew.User, '')
or COALESCE(metroList.Email, '') <> COALESCE(listnew.Email, '')
or COALESCE(metroList.LocName, '') <> COALESCE(listnew.LocName, '')
) THEN
Of course, this assumes that you're fine with NULL meaning the same as an empty string (which may not be appropriate).
Take a look at this BOL article on NULL comparisons.
As I understand the question you are looking for an expression that emulates IS DISTINCT FROM.
The answer you have accepted is not correct then
WITH metroList([User])
AS (SELECT CAST(NULL AS VARCHAR(10))),
listnew([User])
AS (SELECT 'Foo')
SELECT *
FROM metroList
JOIN listnew
ON NULLIF(metroList.[User], listnew.[User]) IS NOT NULL
Returns zero rows. Despite the values under comparison being NULL and Foo.
I would use the technique from this article: Undocumented Query Plans: Equality Comparisons
WHEN MATCHED AND EXISTS (
SELECT metroList.[User], metroList.Email,metroList.LocName
EXCEPT
SELECT listnew.[User], listnew.Email,listnew.LocName
)

SQL Data Filtering approach

I have a stored procedure that receives 3 parameters that are used to dynamically filter the result set
create proc MyProc
#Parameter1 int,
#Parameter2 int,
#Paremeter3 int
as
select * from My_table
where
1 = case when #Parameter1 = 0 then 1 when #Parameter1 = Column1 then 1 else 0 end
and
1 = case when #Parameter2 = 0 then 1 when #Parameter2 = Column2 then 1 else 0 end
and
1 = case when #Parameter3 = 0 then 1 when #Parameter3 = Column3 then 1 else 0 end
return
The values passed for each parameter can be 0 (for all items) or non-zero for items matching on specific column.
I may have upwards of 20 parameters (example shown only has 3). Is there a more elegant approach to allow this to scale when the database gets large?
I am using something similar to your idea:
select *
from TableA
where
(#Param1 is null or Column1 = #Param1)
AND (#Param2 is null or Column2 = #Param2)
AND (#Param3 is null or Column3 = #Param3)
It is generally the same, but I used NULLs as neutral value instead of 0. It is more flexible in a sense that it doesn't matter what is the data type of the #Param variables.
I use a slightly different method to some of the ones listed above and I've not noticed any performance hit. Instead of passing in 0 for no filter I would use null and I would force it to be the default value of the parameter.
As we give the parameters default values it makes them optional which lends itself to better readability when your calling the procedure.
create proc myProc
#Parameter1 int = null,
#Parameter2 int = null,
#Paremeter3 int = null
AS
select
*
from
TableA
where
column1 = coalesce(#Parameter1,column1)
and
column2 = coalesce(#Parameter2, column2)
and
column3 = coalesce(#Parameter3,column3)
That said I may well try out the dynamic sql method next time to see if I notice any performance difference
Unfortunately dynamic SQL is the best solution performance/stability wise. While all the other methods commonly used ( #param is not or Col = #param, COALESCE, CASE... ) work, they are unpredictable and unreliable, execution plan can (and will) vary for each execution and you may find yourself spending lots of hours trying to figure out, why your query performs really bad when it was working fine yesterday.