How to optionally apply a WHERE IN clause based on a variable? - sql

I have a stored procedure that needs to filter by a list of ids that are passed as a comma-delimited list (ie '1,2,3').
I want to apply a WHERE IN clause that will match those ids but ONLY if the variable contains anything (IS NOT NULL AND <> '').
Here's a simplified fiddle of the problem: http://sqlfiddle.com/#!18/5f6be/1
It's currently working for single and multiple ids. But when passing '' or NULL it should return everything but it's not returning anything.
The CTEs and pagination stuff is there for a reason, please provide a solution that doesn't change that.

Using DelimitedSplit8k_lead you could achive this by doing:
CREATE PROC YourProc #List varchar(8000) = NULL AS
BEGIN
SELECT {YourColumns}
FROM YourTable YT
OUTER APPLY dbo.DelimitedSplit8k_Lead(#List,',') DS
WHERE DS.item = YT.YourColumn
OR NULLIF(#List,'') IS NULL
OPTION (RECOMPILE);
END
The OUTER APPLY is used, as if NULL is passed the dataset won't be eliminated. the RECOMPILE is there, as it turns into a "Catch-all query" with the addition of handling the NULL.

Why not use JOIN instead of a subquery in the WHERE clause.
set #userIds = nullif(ltrim(#userIds),'')
select u.*
from Users u
left join string_split(#userIds,',') s on u.Id=s.value
where s.value is not null or #userIds is null

The old school method:
WHERE
(#userIds IS NULL OR #userIds = '' OR U.Id IN (SELECT * FROM STRING_SPLIT(#userIds, ',')))
Add OPTION (RECOMPILE) at the end for this to work and trim the plan.
Edit:
Based on comments, this one generates two table scans. It didn't make a difference in my LocalDb setup but don't rely on it regardless.
WHERE U.Id IN
(
SELECT * FROM STRING_SPLIT(#userIds, ',')
UNION ALL
SELECT U.Id WHERE NULLIF(#userIds, '') IS NULL
)

Related

Using ISNULL in SQL LEFT JOIN to check if the result is null, and if it is, use another value to join

I have a SQL select with a where clause where i want to check if the result is null, and if it is null I want to use another value in the where clause, but i get 0 rows results, even though i know i should get a row as result.
Heres my (updated) SQL code:
DECLARE #LanguageCode NVARCHAR(3);
SET #LanguageCode = 'FR'
SELECT
wi.WorkItemId,
ds.DisplayString AS Team
FROM dbo.WorkItem AS wi
LEFT JOIN dbo.DisplayString AS ds ON ds.ElementID = wi.TierId AND ds.LocaleID = ISNULL(#LanguageCode, 'ENU')
The code above returns data for "#LanguageCode" when there is data to return, but it does not switch to use 'ENU' when there is no data. Thats the problem!
This is also just a sample since this is part of a larger query with lots of left joins where i need the same functionality against "LocaleID". I'm hoping there would be something easy solution to this like the code above.
To clarify what i want to achieve, if the c.LocaleID = #LanguageCode returns null rows i want to use the hardcoded value as in c.LocaleID = ENU.
If i don't use the ISNULL function and only use 'ENU' it returns the expected result.
I would appreciate any help. Thanks.
If I understand correctly, you want one row, either with the specified language code or 'ENU'. If so, use filtering and ORDER BY:
SELECT TOP (1) c.*
FROM dbo.column1 c
WHERE c.rowID = '1234-1234-1234' AND
c.LocaleID IN (#LanguageCode, 'ENU')
ORDER BY (CASE WHEN c.LocaleID = #LanguageCode THEN 1 ELSE 2 END)
I think you're looking for this
select coalesce((SELECT rowID FROM dbo.column1 where c.rowID = '1234-1234-1234' and LocaleID = #LanguageCode),
(SELECT rowID FROM dbo.column1 where c.rowID = '1234-1234-1234' and LocaleID = 'ENU'));
I believe you are looking for something like this:
SELECT *
FROM table_name c
WHERE c.LocaleID =
case when (select count(*)
from table_name tn
where tn.LocaleID = #LanguageCode) = 0 then
'ENU'
else
#LanguageCode
end;
Here is the demo.
Here is a new demo: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=624100ee44f89decc4c6383e92d0a016
In the new demo I have added some more code to show how to use it in left join as a part of the join condition and also I have added the code to show how to use it in where clause when joining two tables and I believe it belongs in the where clause...

Is there a better way to write this gross SQL?

So I'm creating a query for a report that could have several optional filters. I've only included client and station here to keep it simple. Each of these options could be an include or an exclude and could contain NULL, 1, or multiple values. So I split the varchar into a table before joining it to the query.
This test takes about 15 minutes to execute, which... just won't do :p Is there a better way? We have similar queries written with dynamic sql, and I was trying to avoid that, but maybe there's no way around it for this?
DECLARE
#ClientsInc VARCHAR(10) = 'ABCD, EFGH',
#ClientsExc VARCHAR(10) = NULL,
#StationsInc VARCHAR(10) = NULL,
#StationsExc VARCHAR(10) = 'SomeStation'
SELECT *
INTO #ClientsInc
FROM dbo.StringSplit(#ClientsInc, ',')
SELECT *
INTO #ClientsExc
FROM dbo.StringSplit(#ClientsExc, ',')
SELECT *
INTO #StationsInc
FROM dbo.StringSplit(#StationsInc, ',')
SELECT *
INTO #StationsExc
FROM dbo.StringSplit(#StationsExc, ',')
SELECT [some stuff]
FROM media_order mo
LEFT JOIN #ClientsInc cInc WITH(NOLOCK) ON cInc.Value = mo.client_code
LEFT JOIN #ClientsExc cExc WITH(NOLOCK) ON cExc.Value = mo.client_code
LEFT JOIN #StationsInc sInc WITH(NOLOCK) ON sInc.Value = mo.station_name
LEFT JOIN #StationsExc sExc WITH(NOLOCK) ON sExc.Value = mo.station_name
WHERE ((#ClientsInc IS NOT NULL AND cInc.Value IS NOT NULL)
OR (#ClientsExc IS NOT NULL AND cExc.Value IS NULL)
)
AND ((#StationsInc IS NOT NULL AND sInc.Value IS NOT NULL)
OR (#StationsExc IS NOT NULL AND sExc.Value IS NULL)
)
First of all, I always tend to mention Erland Sommarskog's Dynamic Search Conditions in such cases.
However, you already seem to be aware of the two options: one is dynamic SQL. The other is usually the old trick and (#var is null or #var=respective_column). This trick, however, works only for one value per variable.
Your solution indeed seems to work for multiple values. But in my opinion, you are trying too hard to avoid dynamic sql. Your requirements are complex enough to guarantee it. And remember, usually, dynamic sql is harder for you to code, but easier for the server in complex cases - and this one certainly is. Making a performance guess is always risky, but I would guess an improvement in this case.
I would use exists and not exists:
select ...
from media_order mo
where
(
#ClientsInc is null
or exists (
select 1
from string_split(#ClientsInc, ',')
where value = mo.client_code
)
)
and not exist (
select 1
from string_split(#ClientsExc, ',')
where value = mo.client_code
)
and (
#StationsInc is null
or exists (
select 1
from string_split(#StationsInc, ',')
where value = mo.station_name
)
)
and not exist (
select 1
from string_split(#StationsExc, ',')
where value = mo.station_name
)
Notes:
I used buil-in function string_split() rather than the custom splitter that you seem to be using. It is available in SQL Server 2016 and higher, and returns a single column called value. You can change that back to your customer function if you are running an earlier version
as I understand the logic you want, "include" parameters need to be checked for nullness before using exists, while it is unnecessary for "exclude" variables

SQL IN() operator with condition inside

I've got table with few numbers inside (or even empty): #states table (value int)
And I need to make SELECT from another table with WHERE clause by definite column.
This column's values must match one of #states numbers or if #states is empty then accept all values (like there is no WHERE condition for this column).
So I tried something like this:
select *
from dbo.tbl_docs docs
where
docs.doc_state in(iif(exists(select 1 from #states), (select value from #states), docs.doc_state))
Unfortunately iif() can't return subquery resulting dataset. I tried different variations with iif() and CASE but it wasn't successful. How to make this condition?
select *
from dbo.tbl_docs docs
where
(
(select count(*) from #states) > 0
AND
docs.doc_state in(select value from #states)
)
OR
(
(select count(*) from #states)=0
AND 1=1
)
Wouldn't a left join do?
declare #statesCount int;
select #statesCount = count(1) from #states;
select
docs.*
from dbo.tbl_docs docs
left join #states s on docs.doc_state = s.value
where s.value is not null or #statesCount = 0;
In general, whenever your query contains sub-queries, you should stop for five minutes, and think hard about whether you really need a sub-query at all.
And if you've got a server capable of doing that, in many cases it might be better to preprocess the input parameters first, or perhaps use constructs such as MS SQL's with.
select *
from dbo.tbl_docs docs
where exists (select 1 from #states where value = doc_state)
or not exists (select 1 from #state)

Combine Unique Column Values Into One to Avoid Duplicates

For simplicity, assume I have two tables joined by account#. The second table has two columns, id and comment. Each account could have one or more comments and each unique comment has a unique id.
I need to write a t-sql query to generate one row for each account - which I assume means I need to combine as many comments as might exit for each account. This assumes the result set will only show the account# once. Simple?
Sql Server is a RDBMS best tuned for storing data and retrieving data, you can retrieve the desired data with one very simple query but the desired format should be handled with any of the reporting tools available like ssrs or crystal reports
Your query will be a simple inner join something like this
SELECT A.Account , B.Comment
FROM TableA AS A INNER JOIN TableB AS B
ON A.Account = B.Account
Now you can use your reporting tool to Group all the Comments by Account when Displaying data.
I do agree with M. Ali, but if you don't have that option, the following will work.
SELECT [accountID]
, [name]
, (SELECT CAST(Comment + ', ' AS VARCHAR(MAX))
FROM [comments]
WHERE (accountID = accounts.accountID)
FOR XML PATH ('')
) AS Comments
FROM accounts
SQL Fiddle
In my actual project I have this exact situation.
What you need is a solution to aggregate the comments in order to show only one line per account#.
I solve it by creating a function to concatenate the comments, like this:
create function dbo.aggregateComments( #accountId integer, #separator varchar( 5 ) )
as
begin;
declare #comments varchar( max ); set #comments = '';
select #comments = #comments + #separator + YouCommentsTableName.CommentColumn
from dbo.YouCommentsTableNAme
where YouCommentsTableName.AccountId = #accountId;
return #comments;
end;
You can use it on you query this way:
select account#, dbo.aggretateComments( account#, ',' )
from dbo.YourAccountTableName
Creating a function will give you a common place to retrieve your comments. It's a good programming practice.

Sql Server IN Clause Issue

Writing a stored procedure that will have multiple input parameters. The parameters may not always have values and could be empty. But since the possibility exists that each parameter may contain values I have to include the criterion that utilizing those parameters in the query.
My query looks something like this:
SELECT DISTINCT COUNT(*) AS SRM
FROM table p
WHERE p.gender IN (SELECT * FROM Fn_SplitParms(#gender)) AND
p.ethnicity IN (SELECT * FROM Fn_SplitParms(#race)) AND
p.marital_status IN (SELECT * FROM Fn_SplitParms(#maritalstatus))
So my problem is if #gender is empty(' ') the query will return data where gender field is empty when I really want to just ignore p.gender all together. I don't want to have to accomplish this task using IF/ELSE conditional statements because they would be too numerous.
Is there any way to use CASE with IN for this scenario? OR
Is there other logic that I'm just not comprehending that will solve this?
Having trouble finding something that works well...
Thanks!
Use or:
SELECT DISTINCT COUNT(*) AS SRM
FROM table p
WHERE
(p.gender IN (SELECT * FROM Fn_SplitParms(#gender)) OR #gender = '')
AND (p.ethnicity IN (SELECT * FROM Fn_SplitParms(#race)) OR #race = '')
AND (p.marital_status IN (SELECT * FROM Fn_SplitParms(#maritalstatus)) OR #maritalstatus = '')
You might also want to consider table-valued parameters (if using SQL Server 2008 and up) - these can sometimes make the code simpler, since they are treated as tables (which in your case, may be empty) and you can join - plus no awkward split function required.