T-SQL IN Clause Combined With Case Statement - sql

i have query where i have a in clause combined with a case statement.
the error i get is this:
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
i receive in stored procedure a concatenate varchar value like this '2,3,4,5' and i want in my query to filter the ids using the in clause
but something i missing
someone can give me a hand with this pls?
#ids varchar(50) = '2,3,4'
DECLARE #mylist TABLE (Id int)
INSERT INTO #mylist
select CONVERT(INT,v) from [dbo].[SplitString](#cardtypes)
//result of #mylist
//-------------------
//2
//3
//4
and then in the query
select * from mytable
where
ctable.Id IN (CASE
WHEN ISNULL(#ids,'') <> '' THEN (select id from #mylist)
ELSE ctable.Id
END)
Note:
ctable.Id (INT)
If i have only passing one value it works but if there are more it breaks
thanks in advance

You're trying to mix scalar value ctable.Id and some resultset select id from #mylist here in one case statement.
This can't be done, but you can rewrite your query like this:
select * from mytable
where
(isnull(#ids,'') <> '' and ctable.Id in (select id from #mylist))
or isnull(#ids,'') = ''

Out of curiosity, why are you using a temporary table? You can express this as a CTE:
with values as (
select CONVERT(INT, v)
from [dbo].[SplitString](#cardtypes)
)
select t.*
from <table> t
where t.id in (select v from values) or
not exists (select 1 from values);

Related

How can I create a conditional WHERE clause in SQL Server that returns multiple values?

I have the following SQL code for a SSRS report. I simplified the code because the original query is much longer.
There is a parameter #ARTICLE which a user can input. What I want to do is create a conditional WHERE statement. If a user enters an article number (#ARTICLE) the query should filter ID's from Table1 that match with ID's for which the entered article number (#ARTICLE) have a match with a 'detailcode' from another table. If there is no article number given, do not filter (or skip the whole WHERE statement)
With the code below I get the following error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.'
Logically it works perfectly fine without the CASE statement, so when only the subquery is used to check for matching ID's. However, I only want to return matching IDs if the #ARTICLE parameter has a value. If it is NULL or an empty string I want to return all IDs (or just skip the entire WHERE statement). How can I include a condition in the WHERE clause that allows multiple rows to return given the example below?
I feel like my approach is way to complicated, any help is much appreciated!
DECLARE #ARTICLE AS VARCHAR(50) = '1234567'
SELECT * FROM Table1
WHERE
Table1.ID IN (
CASE
WHEN ISNULL(#ARTICLE,'')<>'' THEN
(
SELECT ID
FROM Table2
WHERE detailcode IN (#ARTICLE)
)
ELSE Table1.ID
END
)
You're right, you're overcomplicating it a bit - if you look at the LIKE operator you can do something like:
DECLARE #filter NVARCHAR(50) = '123456';
DECLARE #f NVARCHAR(100) = '%' + #filter + '%';
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField]
AND [t2].[detailCode] LIKE #f;
Where #filter is a parameter to the stored procedure.
Or to account for detailCode being null:
DECLARE #filter NVARCHAR(50) = '123456';
DECLARE #f NVARCHAR(100) = '%' + #filter + '%';
IF #filter != NULL
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField]
AND [t2].[detailCode] LIKE #f;
ELSE
SELECT *
FROM [Table1] AS [t1]
INNER JOIN [Table2] AS [t2]
ON [t2].[joinField] = [t1].[joinField];
I would check wether #ARTICLE is NULL or if it is NOT NULL and your subquery is fulfilled, like so:
WHERE
ISNULL(#ARTICLE, '') = ''
OR
(
ISNULL(#ARTICLE, '') <> ''
AND ID IN
(
SELECT ID FROM Table2
WHERE detailcode = #ARTICLE
)
)
Maybe you can do it entire different, with an exists for example.
So you return all rows when #ARTICLE is null or '' OR exists at least one row in table2 with this article
The OR will have the effect that no filtering is done when the variable is null or ''
DECLARE #ARTICLE AS VARCHAR(50) = '1234567'
select t1.*
from table1 t1
where ( isnull(#ARTICLE, '') = ''
or
exists ( select 1 from table2 t2 where t2.detailcode = #ARTICLE )
)

Case Expression syntax in where clause makes problem that Sub query returned more than 1 value when the sub query is used as an expression

I want to write some code that returns all table records if I select Branch code as empty.
But when I select Branch Code this should return the record only for specific Branch.
declare
#BRANCHCODE varchar(4)
set #BRANCHCODE=''
if (#BRANCHCODE='')
begin
SELECT ID,BRANCHCODE,BRANCHNAME,DEPARTMENTCODE,DEPARTMENTNAME FROM RATINGLOGS
WHERE BRANCHCODE in (SELECT BRANCHCODE FROM RATINGLOGS)
end
else
begin
SELECT ID,BRANCHCODE,BRANCHNAME,DEPARTMENTCODE,DEPARTMENTNAME FROM RATINGLOGS
WHERE BRANCHCODE=#BRANCHCODE
end
Above the code return fine.
Now I want to use this condition in case statement of where clause. Code is given below:
declare
#BRANCHCODE varchar(4)
set #BRANCHCODE=''
SELECT ID,BRANCHCODE,BRANCHNAME,DEPARTMENTCODE,DEPARTMENTNAME FROM RATINGLOGS
WHERE BRANCHCODE in (Case when #BRANCHCODE='' then (SELECT BRANCHCODE FROM RATINGLOGS) else #BRANCHCODE end)
It return fine when I set specific Branch Code . Such as
set #BRANCHCODE='1001'
But its return wrong:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
When I use
set #BRANCHCODE = ''
**It's mention that I use
BRANCHCODE in ()
such as it support more than 1 value. I don't use ( =, !=, <, <= , >, >=). But it's going wrong.**
I need the explanation of the question and if it possible using case statement in where clause what the rewrite code will be?
The in condition is redundant in the first query - the branchcode queried from ratinglogs must by definition be in the list of all the branchcodes selected from ratinglogs, so you can just drop the where clause there entirely.
Having that in mind, you can rewrite the query in the second snippet as so:
SELECT id, branchcode, branchname, depatmentcode, departmentname
FROM ratinglogs
WHERE #branchcode IN (branchcode, '');
The case statement can only return 1 value, so when you use ...then (select branchcode from ratinglogs), it returns all of the values there, and this is an error.
If you want to combine the logic of the two queries into one, you have to use "case" logic but not the actual case statement:
WHERE (
(#BRANCHCODE='' and BRANCHCODE in (SELECT BRANCHCODE FROM RATINGLOGS))
or
(not #BRANCHCODE='' and BRANCHCODE=#BRANCHCODE)
)
You don't need to the IN() operator there, your query can be like
SELECT ID,
BRANCHCODE,
BRANCHNAME,
DEPARTMENTCODE,
DEPARTMENTNAME
FROM RATINGLOGS
WHERE 1 = CASE WHEN #BRANCHCODE = '' THEN 1 ELSE 0 END --#BRANCHCODE = ''
OR
BRANCHCODE = #BRANCHCODE;
The above code will returns all rows if #BRANCHCODE = '', and if it hold another value, it will returns the specific row where BRANCHCODE = #BRANCHCODE.
Since you're asking about the CASE expression, if you want to use IN() operator you can use the other solution by #Mureinik.
If the variable #BRANCHCODE can hold multiple values eg: '1001,1002,1003' then it won't work as you expect, you will need to split the string to rows.
SELECT ID,
BRANCHCODE,
BRANCHNAME,
DEPARTMENTCODE,
DEPARTMENTNAME
FROM RATINGLOGS
WHERE BRANCHCODE = ''
OR
BRANCHCODE IN (SELECT Value FROM STRING_SPLIT(#BRANCHCODE, ','));
note that STRING_SPLIT() function is only available in SQL Server 2016+ versions, if you don't have you need to define your own
you dont need any subquery
SELECT ID,
BRANCHCODE,
BRANCHNAME,
DEPARTMENTCODE,
DEPARTMENTNAME
FROM RATINGLOGS
WHERE (#BRANCHCODE = '' or BRANCHCODE = #BRANCHCODE)
This will return all rows if the variable #BRANCHCODE = '' and otherwise it will only return rows where BRANCHCODE = #BRANCHCODE
Since you declared #BRANCHCODE only as varchar(4) I assume it will always only hold one value.
If you need it to hold more than one code, than this wont work

Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=,

I am trying to read multiple values from parameter, store it in a variable and later use it in the select statement to pull multiple values.
declare #codes XML = N'<Root>
<List Value="120" />
<List Value="110" />
</Root>';
declare #codeList VARCHAR(MAX) = (SELECT T.Item.value('#Value[1]','VARCHAR(MAX)') FROM #codes.nodes('/Root/List') AS T(Item));
WITH CODE_RESULT AS(
SELECT ID, Name, Region,
FROM dbo.MyTable1
WHERE
(#codes IS NULL OR DataCode IN ( #codeList))
...
UNION
SELECT ID, Name, Region,
FROM dbo.MyTable2
WHERE
(#codes IS NULL OR DataCode IN ( #codeList))
...
But, I am getting the following exception:
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
Expectation:
How do I store the parameter values in a variable and later reuse it in multiple select statements under 'in' clause? Any suggestion is appreciated.
If you need to store multiple values for use later, use a TABLE VARIABLE to hold the results, like this:
DECLARE #dataCodes TABLE (DataCode varchar(MAX));
INSERT INTO #dataCodes
SELECT T.Item.value('#Value[1]', 'varchar(max)')
FROM #codes.nodes('/Root/List') AS T(Item);
You can then use the "WHERE DataCode IN (SELECT DataCode FROM #dataCodes)" to do the select and get all the results.
Your problem is here:
declare #codeList VARCHAR(MAX) = (SELECT T.Item.value('#Value[1]','VARCHAR(MAX)') FROM #codes.nodes('/Root/List') AS T(Item));
Your select returns two results (120, 110), but a scalar VARCHAR variable can only hold one.
You can fix it by creating a table variable:
DECLARE #codeList TABLE (code varchar(100));
INSERT #codeList
SELECT
T.Item.value('#Value[1]','VARCHAR(MAX)')
FROM #codes.nodes('/Root/List') AS T(Item);
And then change (#codes IS NULL OR DataCode IN ( #codeList)) to (#codes IS NULL OR DataCode IN (SELECT code FROM #codeList))
Here, a LEFT JOIN should perform better than your current syntax, e.g.
WITH CODE_RESULT AS(
SELECT ID, Name, Region,
FROM dbo.MyTable1 t1
LEFT JOIN #codeList cl
ON t1.DataCode = cl.code
You may have to add additional logic to check if cl.code is null depending on your intentions.
You create a table variable
SQL Fiddle Demo
Declare #T_variable table(name varchar(200));
insert into #T_variable
SELECT ID FROM Table1;
SELECT *
FROM YourTable
WHERE ID NOT IN
(SELECT *
FROM #T_variable)
;
GO

Using "IN" operator in a dynamic query

i've been searching SO for a while and found nothing related to this.
We use heavily the dynamic approach for most of our queries like this:
Declare #ContactId VarChar(8000)
Select #ContactId = '1'
Select *
From Person.Contact
Where 1 = 1 And
Case When Len(Ltrim(Rtrim(#ContactId))) = 0 Then 1 Else ContactID End =
Case When Len(Ltrim(Rtrim(#ContactId))) = 0 Then 1 Else #ContactId End
This way the query gets filtered dynamically if there's a value on the parameter
But the problem comes when trying the same stuff with several IDs like so:
Declare #ContactId VarChar(8000)
Select #ContactId = '1,2,3'
Select *
From Person.Contact
Where 1 = 1 And
Case When Len(Ltrim(Rtrim(#ContactId))) = 0 Then 1 Else ContactID End In (
Select Case When Len(Ltrim(Rtrim(#ContactId))) = 0 Then 1 Else ( Select id From dbo.SplitString(#ContactId,',') ) End )
Sql throws an error "Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression."
that's totally normal and expected, my question is:
Is there a way to dynamically do this kind of filtering ?
The solution we use is this:
Declare #Table Table(
Col1 Int,
Col2 Int,
Col3 Int
)
Insert #Table
Select Col1, Col2, Col3
From Person.Contact
Where [many filters except the in clause filters]
If Len(Ltrim(RTrim(#ContactId))) > 0
Select *
From #Table
Where ContactId In ( Select Id From dbo.SplitString(#ContactId, ',') )
Else
Select *
From #Table
But it's not an option when the query is massive and feeding a table variable is overkill for this. Hope i made my point and someone is kind enough to help me find a solution to this.
PS: Using sp_ExecuteSql is not an option in this scenario either, sorry.
How you should do this is with a table parameter.
But if you want to persist in this approach.
Declare #ContactId VarChar(8000)
Select #ContactId = '1,2,3'
Select *
From Person.Contact
where ',' + #contactID + ',' like '%,'+convert(varchar(50),contactid)+',%'
Try this
Select *
From Person.Contact
Where (
len(#contactid) > 0 AND
ContactID IN (Select id From dbo.SplitString(#ContactId,',')))
or 1 = 1

SQL Server variables

Why do these queries return different values? The first returns a result set as expected, but the second (which as far as I can tell is exactly the same) does not. Any thoughts?
1:
declare #c varchar(200)
set #c = 'columnName'
select top 1 *
from myTable
where #c is not null
and len(convert(varchar, #c)) > 0
2:
SELECT top 1 *
FROM myTable
WHERE columnName IS NOT NULL
and len(convert(varchar,columnName)) > 0
It's because they aren't the same query -- your variable text does not get inlined into the query.
In query 1 you are validating that #c is not null (true, you set it) and that its length is greater than 0 (true, it's 10). Since both are true, query 1 becomes:
select top 1 * from myTable
(It will return the first row in myTable based on an appropriate index.)
EDIT: Addressing the comments on the question.
declare #myTable table
(
columnName varchar(50)
)
insert into #myTable values ('8')
declare #c nvarchar(50)
set #c = 'columnName'
select top 1 *
from #myTable
where #c is not null
and len(convert(varchar, #c)) > 0
select top 1 *
from #myTable
where columnName is not null
and len(convert(varchar,columnName)) > 0
Now when I run this both queries return the same result. You'll have to tell me where I'm misrepresenting your actual data / query to get more help (or just expand upon this to find a solution).
In the first query, you are checking the value 'columnName' against the parameters IS NOT NULL and length > 0. In the second query, you are checking the values in the columnName column against those parameters.
It should be noted that query 1 will always return one row (assuming a row exists), where query 2 will only return a row if the contents of columnName are not null and length > 0.
The first query actually evaluates as
select top 1 * from myTable where 'columnName' is not null and len(convert(varchar, 'columnName' )) > 0
Not as what you were hoping for.expected.
The two querys are not the same as in the second query you are evaluating the actual value of the field columnname. The following is the equivalent of your first function.
SELECT top 1 * FROM myTable WHERE 'columnName' IS NOT NULL and len(convert(varchar,'columnName')) > 0
They're not the same - the first is checking the variable while the second is checking the column. "where #c is not null" means where the variable #c isn't null - which it isn't, since it contains the value 'columnName'. "where columnName is not null" means where the field columnName doesn't contain a null. And the same for the evaluation of the length.