tsql IN not working properly if passed as parameter - sql

I have a sitution where I need to pass a string in order to do an IN
declare #searchString varchar(50)
set #searchString = ' ''Nestle'',''UFI'' '
select * from tbl1 where CompanyName IN (#SearchString)
does't work.
But if I do:
select * from tbl1 where CompanyName IN ('Nestle','UFI')
It works fine. I cannot understand why one work and the other doesn't

When you use IN it looks at a set and not a single string expression. Because of this
you need to implement a function such as CsvToInt to return a table, like so:
CREATE Function [dbo].[CsvToInt] ( #Array varchar(1000))
returns #IntTable table
(IntValue int)
--Parse comma seperated value parameters
--To be used in SELECT blah WHERE blah IN (...)
--This function returns int, but may be modified to return any datatype
AS
begin
declare #separator char(1)
set #separator = ','
declare #separator_position int
declare #array_value varchar(1000)
set #array = #array + ','
while patindex('%,%' , #array) <> 0
begin
select #separator_position = patindex('%,%' , #array)
select #array_value = left(#array, #separator_position - 1)
Insert #IntTable
Values (Cast(#array_value as int))
select #array = stuff(#array, 1, #separator_position, '')
end
return
end
And then you can use this function from say within a stored procedure like so:
SELECT Blah
FROM MyTable
WHERE Foo IN (SELECT * FROM dbo.CsvToInt(#Parameter))
Where #Parameter contains a comma seperated string of values like:
Nestle, UFI, Test

IN spects a set, not an string as parameter.
See this:
http://social.msdn.microsoft.com/forums/en-US/transactsql/thread/33108337-b7b4-4ada-a480-60673e175f4d/

Your example that works is not the same as your example that does not work. Your example that works has a list of strings. Your example that does not work is just a single string. This means that you are trying to retrieve rows where the CompanyName field is equal to the whole string, not the comma delimited portions of that string.
Your example that does not work is equal to:
select * from tbl1 where CompanyName IN (' ''Nestle'',''UFI'' ')
not the following:
select * from tbl1 where CompanyName IN ('Nestle','UFI')

declare #searchString varchar(50)
set #searchString = ' ''Nestle'',''UFI'' '
exec ('select * from tbl1 where CompanyName IN (' + #SearchString + ');')
Note that this is dynamic SQL which is sometimes frowned upon and does not optimize well, but it's simple and will get what you want.

declare #searchString varchar(50)
set #searchString = 'Nestle,UFI'
set #searchString = ',' + #searchString + ','
select * from tbl1 where charindex(',' + CompanyName + ',',#SearchString) > 0

Related

Comparing Comma Separate Value with Comma Separated Values SQL

I have a table A which contain column "ColumnName" (it will contain the comma separated integer values ) and I have store procedure which take parameter which is also comma separated integer values.
For example I have values in table "101,102,103" and "103,104,105" and User input is "101,104" it should return 2 record. how can I achieve this?
Need SQL statements
This should do it:
CREATE PROCEDURE GetMatches(#input varchar (100))
AS
BEGIN
WITH CTE AS
(
SELECT value AS number
FROM STRING_SPLIT(#input, ',')
)
SELECT CTE.number, A.ColumnName
FROM A
INNER JOIN CTE
ON ',' + A.ColumnName + ',' LIKE '%,' + CTE.number + ',%';
END
You can test the stored procedure like this:
EXEC dbo.GetMatches #input = '101,104';
If you need help, explanations, let me know :)
DECLARE
#input VARCHAR(MAX) = '101,104',
#separator CHAR(1) =',',
#separatorPosition INT,
#value VARCHAR(MAX),
#start INT = 1
DECLARE #split_input TABLE(Value VARCHAR(MAX))
DECLARE #tmp TABLE (st VARCHAR(50))
INSERT INTO #tmp VALUES ('101,102,103')
INSERT INTO #tmp VALUES ('103,104,105')
INSERT INTO #tmp VALUES ('106,107,108')
SET #separatorPosition = CHARINDEX(#separator, #input)
IF #separatorPosition = 0
BEGIN
INSERT INTO #split_input
VALUES
(
#input
)
RETURN
END
SET #input = #input + #separator
WHILE #separatorPosition > 0
BEGIN
SET #value = SUBSTRING(#input, #start, #separatorPosition - #start)
IF (#value <> '')
INSERT INTO #split_input
VALUES
(
#value
)
SET #start = #separatorPosition + 1
SET #separatorPosition = CHARINDEX(#separator, #input, #start)
END
SELECT tmp.st FROM #tmp AS tmp INNER JOIN #split_input AS split ON tmp.st LIKE '%' + split.Value + '%'
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[fnParseArray] (#Array VARCHAR(MAX),#separator CHAR(1))
RETURNS #T Table (col1 varchar(50))
AS
BEGIN
-- #Array is the array we wish to parse
-- #Separator is the separator charactor such as a comma
DECLARE #separator_position INT -- This is used to locate each separator character
DECLARE #array_value VARCHAR(MAX) -- this holds each array value as it is returned
-- For my loop to work I need an extra separator at the end. I always look to the
-- left of the separator character for each array value
SET #array = #array + #separator
-- Loop through the string searching for separtor characters
WHILE PATINDEX('%' + #separator + '%', #array) <> 0
BEGIN
-- patindex matches the a pattern against a string
SELECT #separator_position = PATINDEX('%' + #separator + '%',#array)
SELECT #array_value = LEFT(#array, #separator_position - 1)
-- This is where you process the values passed.
INSERT into #T VALUES (#array_value)
-- Replace this select statement with your processing
-- #array_value holds the value of this element of the array
-- This replaces what we just processed with and empty string
SELECT #array = STUFF(#array, 1, #separator_position, '')
END
RETURN
END
select * from [dbo].[fnParseArray]('a,b,c,d',',')

Using a comma-separated parameter in an IN clause

I have 'param1, param2, parma3' coming from SSRS to a stored procedure as a varchar parameter: I need to use it in a query's IN clause but then need to change its format like this first:
select *
from table1
where col1 in('param1', 'param2', 'param3')
What is the best way to reformat the parameter without creating functions and parameter tables?
Try this one, Just need to add commas at the beginning and at the end of #params string.
Declare #params varchar(100) Set #params = ',param1,param2,param3,'
Select * from t
where CHARINDEX(','+cast(col1 as varchar(8000))+',', #params) > 0
SQL FIDDLE
you can use split function and use it as in following way
here my split fnSplitString return splitdata
select * from tb1 where id in(select splitdata from dbo.fnSplitString((select col1 from tb12 where id=3),','))
create FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output(splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
If you are using SQL 2016 and above string_split you can use.
-- #param is where you keep your comma separated values example:
declare #param = 'param1,param2,param3'
select * from table1 where col1 in (select TRIM(value) from string_split(#param,',')
More information about string_split check offical documemt
Furthermore, TRIM() is used to trim values from white spaces.
We can use STRING_SPLIT() in SQL SERVER
DECLARE #params varchar(max)= 'param1,param2,param3'
SELECT *
FROM table1
WHERE col1 IN (SELECT value FROM STRING_SPLIT( #params , ','))
"Best way" is arguable, but one classic approach that remains without "creating functions and table parameters" is to simply employ dynamic SQL in the stored procedure:
-- FORNOW: local to act as the SP param and arg
declare #values varchar(100) = 'param1, param2, param3'
-- Add opening and closing single quotes, then quotes around each
-- comma-separated list item.
select #values = '''' + REPLACE(#values, ', ', ''', ''') + ''''
-- FORNOW: for clarity/debugging
print #values
--'param1', 'param2', 'param3'
-- Run the desired query as dynamic SQL.
DECLARE #sql as nvarchar(250);
SET #sql = 'select * from table1 where col1 in (' + #values + ')';
EXEC sp_executesql #sql;
This assumes a couple things, though:
That commas in the list of values are followed by a space. Variations on this solution can address deviations in this respect of course, but it is important to be aware of this assumption.
That the comma-separated values do not themselves have commas in them – unlikely but worth mentioning since whether values will satisfy this constraint sometimes goes unconsidered.
Load the Params into a string and execute as an sql :
declare #param varchar(1000) = 'param1, param2, parma3'
declare #sql varchar(4000)
select #sql =
'select *
from table1
where col1 in(''' + replace(#param,',',''',''') + ''')'
-- print #sql -- to see what you're going to execute
exec sp_executesql #sql
DECLARE #params varchar(max) = '1,2,3,4'
SELECT * FROM table2 WHERE colId IN (SELECT value FROM SPLIT(#params,','))
Base on id we can find.

SQL Server procedure declare a list

My SQL code is fairly simple. I'm trying to select some data from a database like this:
SELECT * FROM DBTable
WHERE id IN (1,2,5,7,10)
I want to know how to declare the list before the select (in a variable, list, array, or something) and inside the select only use the variable name, something like this:
VAR myList = "(1,2,5,7,10)"
SELECT * FROM DBTable
WHERE id IN myList
You could declare a variable as a temporary table like this:
declare #myList table (Id int)
Which means you can use the insert statement to populate it with values:
insert into #myList values (1), (2), (5), (7), (10)
Then your select statement can use either the in statement:
select * from DBTable
where id in (select Id from #myList)
Or you could join to the temporary table like this:
select *
from DBTable d
join #myList t on t.Id = d.Id
And if you do something like this a lot then you could consider defining a user-defined table type so you could then declare your variable like this:
declare #myList dbo.MyTableType
That is not possible with a normal query since the in clause needs separate values and not a single value containing a comma separated list. One solution would be a dynamic query
declare #myList varchar(100)
set #myList = '1,2,5,7,10'
exec('select * from DBTable where id IN (' + #myList + ')')
You can convert the list of passed values into a table valued parameter and then select against this list
DECLARE #list NVARCHAR(MAX)
SET #list = '1,2,5,7,10';
DECLARE #pos INT
DECLARE #nextpos INT
DECLARE #valuelen INT
DECLARE #tbl TABLE (number int NOT NULL)
SELECT #pos = 0, #nextpos = 1;
WHILE #nextpos > 0
BEGIN
SELECT #nextpos = charindex(',', #list, #pos + 1)
SELECT #valuelen = CASE WHEN #nextpos > 0
THEN #nextpos
ELSE len(#list) + 1
END - #pos - 1
INSERT #tbl (number)
VALUES (convert(int, substring(#list, #pos + 1, #valuelen)))
SELECT #pos = #nextpos;
END
SELECT * FROM DBTable WHERE id IN (SELECT number FROM #tbl);
In this example the string passed in '1,2,5,7,10' is split by the commas and each value is added as a new row within the #tbl table variable. This can then be selected against using standard SQL.
If you intend to reuse this functionality you could go further and convert this into a function.
I've always found it easier to invert the test against the list in situations like this. For instance...
SELECT
field0, field1, field2
FROM
my_table
WHERE
',' + #mysearchlist + ',' LIKE '%,' + CAST(field3 AS VARCHAR) + ',%'
This means that there is no complicated mish-mash required for the values that you are looking for.
As an example, if our list was ('1,2,3'), then we add a comma to the start and end of our list like so: ',' + #mysearchlist + ','.
We also do the same for the field value we're looking for and add wildcards: '%,' + CAST(field3 AS VARCHAR) + ',%' (notice the % and the , characters).
Finally we test the two using the LIKE operator: ',' + #mysearchlist + ',' LIKE '%,' + CAST(field3 AS VARCHAR) + ',%'.
Alternative to #Peter Monks.
If the number in the 'in' statement is small and fixed.
DECLARE #var1 varchar(30), #var2 varchar(30), #var3 varchar(30);
SET #var1 = 'james';
SET #var2 = 'same';
SET #var3 = 'dogcat';
Select * FROM Database Where x in (#var1,#var2,#var3);
If you want input comma separated string as input & apply in in query in that then you can make Function like:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end;
You can use it like :
Declare #Values VARCHAR(MAX);
set #Values ='1,2,5,7,10';
Select * from DBTable
Where id in (select items from [dbo].[Split] (#Values, ',') )
Alternatively if you don't have comma-separated string as input, You can try Table variable OR TableType Or Temp table like: INSERT using LIST into Stored Procedure

Spliting large string in Sql Server

I am using this logic to split the string query
declare #query nvarchar(max)
set #query = '1&2&3&4&5&6&7&8&9&10&11&12&13&14'
SELECT SUBSTRING('&' + #query + '&', Number + 1, -- is used to split the '#query' on the basis of '&' sign
CHARINDEX('&', '&' + #query + '&', Number + 1) - Number -1)AS VALUE
FROM master..spt_values
WHERE Type = 'P'
AND Number <= LEN('&' + #query + '&') - 1
AND SUBSTRING('&' + #query + '&', Number, 1) = '&'
It works fine when query is small, but its giving me less result then actual when the value of #query is very large
For eg.
#query = 'very large string containing 60 & sign '
returns only 10 records
How can I split large string, and what is the reason? Why can SUBSTRING not handle large strings?
You can use this function. It returns a table of splitted values, based on input string and a delimeter.
Usage:
select *
from dbo.fn_ParseText2Table('1&2&3&4&5&6&7&8&9&10&11&12&13&14','&')
The function has a parameter #p_SourceText which type is varchar(8000), so input string can be up to 8000 characters.
You can change type to varchar(max) if version of your SQL Server allows you to do this.
I used this Function and its working for me Perfectly :)
CREATE function f_split(#SourceSql nvarchar(Max),#StrSeprate varchar(10))
returns #temp
table([column] nvarchar(Max))
begin
declare #i int
set #SourceSql=rtrim(ltrim(#SourceSql))
set #i=charindex(#StrSeprate,#SourceSql)
while #i>=1
begin
insert #temp values(left(#SourceSql,#i-1))
set #SourceSql=substring(#SourceSql,#i+1,len(#SourceSql)-#i)
set #i=charindex(#StrSeprate,#SourceSql)
end
if #SourceSql<>'\'
insert #temp values(#SourceSql)
return
end
go
select * from dbo.f_split('1,2,3,4,5',',')
go
I am not sure why your code is not getting the result. I executed the query with large amount of data. But I got the result. May be your string is very much bigger than I did the testing. I have also the same requirement to split the string. I am using this function to get the solution. You can try this.
CREATE FUNCTION [dbo].[fnSplitString] ( #MyArray VARCHAR(8000), #separator CHAR(1) )
RETURNS #RetTable TABLE
(StrValue VARCHAR(256))
AS
BEGIN
DECLARE #SeperatorString VARCHAR(10);
SET #SeperatorString = '%' + #separator + '%'
DECLARE #separator_position INT
DECLARE #array_value VARCHAR(1000)
SET #MyArray = #MyArray + #separator
WHILE PATINDEX( #SeperatorString , #MyArray) <> 0
BEGIN
SELECT #separator_position = PATINDEX(#SeperatorString , #MyArray)
SELECT #array_value = LEFT(#MyArray, #separator_position - 1)
INSERT #RetTable VALUES ( CAST(#array_value AS VARCHAR(256)) )
SELECT #MyArray = STUFF(#MyArray, 1, #separator_position, '')
END
RETURN
END
If you want more explanation on this function, how the function is using and what the parameters are , you can take a look here.. This is very simple function and easy to use.

How can I implement Stored Procedure that accept dynamic search criteria?

Suppose I have the below table ( TestTable ) :
ID , SystemID , UserID ( all columns are of type int )
I want to write a stored procedure that should accept a string parameter; its value like ((5 and 6) or 7) to return all users that apply the below queries :
Select * From TestTable Where SystemID = 5
Intersect
Select * From TestTable Where SystemID = 6
and the above result is union with
Select * From TestTable Where SystemID = 7
SP must accept any combination like (((4 or 5) and 6) or 8) , (((5 or 9) or 8) and 10) .. etc
How can I implement that ?
Update : my issue isn't how to split the string .. but how can i make dynamic sql to implement it's logical mean
DECLARE #param NVARCHAR(MAX) = N'4 or 5 and 6 or 8 and 10';
DECLARE
#sql NVARCHAR(MAX) = N'',
#q NVARCHAR(MAX) = N'SELECT UserID FROM dbo.TestTable WHERE SystemID = ';
SELECT #sql = #q + REPLACE(REPLACE(#param, ' or ', '
UNION ALL ' + #q),
' and ', '
INTERSECT ' + #q);
PRINT #sql;
-- EXEC sp_executesql #sql;
Results:
SELECT UserID FROM dbo.TestTable WHERE SystemID = 4
UNION ALL SELECT UserID FROM dbo.TestTable WHERE SystemID = 5
INTERSECT SELECT UserID FROM dbo.TestTable WHERE SystemID = 6
UNION ALL SELECT UserID FROM dbo.TestTable WHERE SystemID = 8
INTERSECT SELECT UserID FROM dbo.TestTable WHERE SystemID = 10
Now, whether this query yields the results you're actually after, I have no idea, but I believe it meets the requirements as stated.
Try this... I have little changed Aaron Bertrand's query.
DECLARE #param NVARCHAR(MAX) = N'(((4 or 5) and 6) or 8)';
DECLARE #sql NVARCHAR(MAX) = N'',
#q NVARCHAR(MAX) = N'SELECT * FROM dbo.TestTable WHERE SystemID = ',
#paranth NVARCHAR(100) = substring(#param,0,PATINDEX('%[0-9]%',#param));
set #param =substring(#param,PATINDEX('%[0-9]%',#param),len(#param)-PATINDEX('%[0-9]%',#param))
SELECT #sql = #q + REPLACE(REPLACE(#param, ' or ', '
UNION ALL ' + #q),
' and ', '
INTERSECT ' + #q);
set #sql=#paranth+#sql
if (isnull(#paranth,'')<>'')
set #sql=#sql+')'
PRINT #sql;
You could use a CSV to Integer table value function inside your SP. You have to create the CsvToInt function first of course. Then you can use it inside your Stored Procedure to turn a parameter into an integer list. As pointed out this only suits the "Or" component of your dynamic search criteria.
You can use this in conjunction with EXEC or sp_executesql. Which will allow you to add sql as a parameter.
SET #myBaseQuery = 'SELECT * FROM TestTable WHERE SystemId = ' + #myParam
EXECUTE(#myBaseQuery)
or
SELECT * FROM TestTable WHERE SystemID IN (SELECT IntValue FROM dbo.CsvToInt('2,3,4,5,6'))
-- use your parameters
CREATE FUNCTION [dbo].[CsvToInt] ( #Array VARCHAR(1000))
RETURNS #IntTable TABLE
(IntValue INT)
AS
BEGIN
DECLARE #separator CHAR(1)
SET #separator = ','
DECLARE #separator_position INT
DECLARE #array_value VARCHAR(1000)
SET #array = #array + ','
While patindex('%,%' , #array) <> 0
BEGIN
SELECT #separator_position = patindex('%,%' , #array)
SELECT #array_value = LEFT(#array, #separator_position - 1)
INSERT #IntTable
VALUES (CAST(#array_value AS INT))
SELECT #array = stuff(#array, 1, #separator_position, '')
END
RETURN
END
CsvToInt function taken from http://www.summit-pro.com/blog/2010/05/18/csv-list-to-int-sql-function/