Pass a SQL variable into an 'IN' clause through stored procedure - sql

My variable looks like this:
DECLARE #number AS NVARCHAR(1000)
SET #number = '1,2,3,4,5,6'
What I have in my WHERE statement is this:
WHERE V.client IN (#number)
My V.Client column is an integer. What I am trying to do is remove the '' from the #number and make it look like 1,2,3,4,5,6. I have tried to do this,
',' + #number + ',' LIKE '%,' + CAST(V.client AS VARCHAR) + ',%'
but it only returns results for 1 and not the other numbers.
Does anyone have any ideas as to what I can do?

Another option
DECLARE #number AS nvarchar(1000)
SET #number = '1,2,3,4,5,6'
...
WHERE V.client IN (
Select RetVal = B.i.value('(./text())[1]', 'int')
From (Select x = Cast('<x>' + replace(#Number,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
)
If 2016+, use string_split()

Dynamic sql is the most common option, here's a snippet how to use it
DECLARE #number AS nvarchar(1000)
SET #number = N'1,2,3,4,5,6'
declare #cmd nvarchar(1000) = N'select case when 1 in ' + Quotename(#number, '()') + ' then 1 else 0 end'
exec sp_executesql #cmd
which will prepare following query
select case when 1 in (1,2,3,4,5,6) then 1 else 0 end
Other options you can consider:
Split the string and use standard sql queries.
In new sql server there is function string_split which make exacly what you want https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-2017.
In older versions you can create custom fuction doing exactly the same thing How do I split a string so I can access item x?

Related

how to find all strings in between commas in sql server

I want to display a string in a table format as shown below:
For a string like 'hi,is,1,question,thanks,.,.,n'
I need this result:
column1 column2 column3 column4 ..... column
hi is 1 question ..... n
DECLARE #string VARCHAR(MAX);
SET #string = 'hi,is,1,question,thanks,.,.,n';
DECLARE #SQL VARCHAR(MAX);
SET #SQL = 'SELECT ''' + REPLACE(#string, ',', ''',''') + '''';
EXEC (#SQL);
Result:
Add SELECT ' at beginning and ' at the end of string
Replace all , with ',' inside string
So string 'hi,is,1,question,thanks,.,.,n' is replace by 'SELECT 'hi','is','1','question','thanks','.','.','n''
Executed as SQL query
PS: If you want to use it on column you will have to combine it with CURSOR
Update
DECLARE #table TABLE
(
ID INT IDENTITY,
string VARCHAR(MAX)
);
INSERT INTO #table
VALUES
('This,is,a,string,,n,elements,..');
INSERT INTO #table
VALUES
('And,one,more');
INSERT INTO #table
VALUES
('Ugly,but,works,,,Yay!,..,,,10,11,12,13,14,15,16,17,18,19,..');
SELECT * FROM #table
DECLARE #string_to_split VARCHAR(MAX);
DECLARE #sql_query_to_execute VARCHAR(MAX);
DECLARE #max_elements INT, #id INT, #i INT;
SET #i = 1;
DECLARE string_cursor CURSOR FOR SELECT ID, string FROM #table;
SELECT #max_elements = MAX(LEN(string) - LEN(REPLACE(string, ',', ''))) + 1 -- Find max number of elements */
FROM #table;
IF OBJECT_ID('tempdb..##my_temp_table_for_splitted_columns') <> 0 -- Create new temp table with valid amount of columns
DROP TABLE ##my_temp_table_for_splited_columns;
SET #sql_query_to_execute = 'create table ##my_temp_table_for_splitted_columns ( ID int,';
WHILE #i <= #max_elements
BEGIN
SET #sql_query_to_execute = #sql_query_to_execute + ' Col' + CAST(#i AS VARCHAR(max)) + ' varchar(25), ';
SET #i = #i + 1;
END;
SELECT #sql_query_to_execute = SUBSTRING(#sql_query_to_execute, 1, LEN(#sql_query_to_execute) - 1) + ')';
EXEC (#sql_query_to_execute);
/* Split string for each row */
OPEN string_cursor;
FETCH NEXT FROM string_cursor
INTO #id,
#string_to_split
WHILE ##FETCH_STATUS = 0
BEGIN
SET #i = MAX(LEN(#string_to_split) - LEN(REPLACE(#string_to_split, ',', ''))) + 1; -- check amount of columns for current string
WHILE #i < #max_elements
BEGIN
SET #string_to_split = #string_to_split + ','; -- add missing columns
SET #i = #i + 1;
END;
SET #sql_query_to_execute = 'SELECT ' + CAST(#id AS VARCHAR(MAX)) + ',''' + REPLACE(#string_to_split, ',', ''',''') + '''';
INSERT INTO ##my_temp_table_for_splitted_columns --insert result to temp table
EXEC (#sql_query_to_execute);
FETCH NEXT FROM string_cursor
INTO #id,
#string_to_split;
END;
CLOSE string_cursor;
DEALLOCATE string_cursor;
SELECT *
FROM ##my_temp_table_for_splitted_columns;
This is not trivial. You will find a lot of examples how to split your string in a set of fragments. And you will find a lot of examples how to pivot a row set to a single row. But - adding quite some difficulty - you have an unknown count of columns. There are three approaches:
Split this and return your set with a known maximum of columns
Use a dynamically created statement and use EXEC. But this will not work in VIEWs or iTVFs, nor will it work against a table.
Instead of a column list you return a generic container like XML
with a known maximum of columns
One example for the first was this
DECLARE #str VARCHAR(1000)='This,is,a,string,with,n,elements,...';
SELECT p.*
FROM
(
SELECT A.[value]
,CONCAT('Column',A.[key]+1) AS ColumnName
FROM OPENJSON('["' + REPLACE(#str,',','","') + '"]') A
) t
PIVOT
(
MAX(t.[value]) FOR ColumnName IN(Column1,Column2,Column3,Column4,Column5,Column6,Column7,Column8,Column9 /*add as many as you need*/)
) p
Hint: My approach to split the string uses OPENJSON, not available before version 2016. But there are many other approaches you'll find easily. It's just an example to show you the combination of a splitter with PIVOT using a running index to build up a column name.
Unknown count of columns
And the same example with a dynamically created column list was this:
DECLARE #str VARCHAR(1000)='This,is,a,string,with,n,elements,...';
DECLARE #CountElements INT=LEN(#str)-LEN(REPLACE(#str,',',''))+1;
DECLARE #columnList NVARCHAR(MAX)=
STUFF((
SELECT TOP(#CountElements)
CONCAT(',Column',ROW_NUMBER() OVER(ORDER BY (SELECT 1)))
FROM master..spt_values /*has a lot of rows*/
FOR XML PATH('')
),1,1,'');
DECLARE #Command NVARCHAR(MAX)=
N'SELECT p.*
FROM
(
SELECT A.[value]
,CONCAT(''Column'',A.[key]+1) AS ColumnName
FROM OPENJSON(''["'' + REPLACE(''' + #str + ''','','',''","'') + ''"]'') A
) t
PIVOT
(
MAX(t.[value]) FOR ColumnName IN(' + #columnList + ')
) p;';
EXEC(#Command);
Hint: The statement created is exactly the same as above. But the column list in the pivot's IN is created dynamically. This will work with (almost) any count of words generically.
If you need more help, please use the edit option of your question and provide some more details.
An inlineable approach for a table returning a generic container
If you need this against a table, you might try something along this:
DECLARE #tbl TABLE(ID INT IDENTITY,YourList NVARCHAR(MAX));
INSERT INTO #tbl VALUES('This,is,a,string,with,n,elements,...')
,('And,one,more');
SELECT *
,CAST('<x>' + REPLACE((SELECT t.YourList AS [*] FOR XML PATH('')),',','</x><x>') + '</x>' AS XML) AS Splitted
FROM #tbl t
This will return your list as an XML like
<x>This</x>
<x>is</x>
<x>a</x>
<x>string</x>
<x>with</x>
<x>n</x>
<x>elements</x>
<x>...</x>
You can grab - if needed - each element by its index like here
TheXml.value('/x[1]','nvarchar(max)') AS Element1

Returning all child nodes as columns from XML in Sql Server

I am doing the following to select nodes from an XML string, the first part is just to show you what I'm selecting from.
The issue is I want to do this for various different XML columns and I'd like to not have to specify the node name for each column in my select, is there a way to select all nodes as columns automatically or even a cursor using count?
DECLARE #MyXML XML
SET #MyXML = (SELECT
CAST (
'<AllowAdd>N</AllowAdd>
<Allowed>NUMSEG</Allowed>
<AllSegmentsEqualValue>N</AllSegmentsEqualValue>
<ClusterLevelSA>Y</ClusterLevelSA>
<ClusterLevelPremium>Y</ClusterLevelPremium>
<AllowAssignedAndInTrust>N</AllowAssignedAndInTrust>
<MinSegments>1</MinSegments>
<MaxSegments>100</MaxSegments>
<DefaultSegments>10</DefaultSegments>
<RoundPremiumsTo>2</RoundPremiumsTo>
<TaxDeferredAllowance>0.05</TaxDeferredAllowance>
<HigherTaxValueBands>HTVB</HigherTaxValueBands>
<NumberYearsCalculationType>NONFIN</NumberYearsCalculationType>
<OnShore>POLICY</OnShore>
<OffShore>NONFIN</OffShore>'as XML) as x)
SELECT
Data.Col.value('(/AllowAdd)[1]','Varchar(10)') as [Allow Addition of]
,Data.Col.value('(/Allowed)[1]','Varchar(10)') as [Allowed]
,Data.Col.value('(/MinSegments)[1]','Int') as [Min Segments]
,Data.Col.value('(/MaxSegments)[1]','Int') as [Max Segments]
,Data.Col.value('(/DefaultSegments)[1]','Int') as [Default Segments]
,Data.Col.value('(/RoundPremiumsTo)[1]','Int') as [Round Premiums To]
,Data.Col.value('(/AllSegmentsEqualValue)[1]','Varchar(10)') as [All Segments Equal Value]
--,Data.Col.value('(/TaxDeferredAllowance)[1]','Varchar(10)') as [Tax Deferred Allowance]
,Data.Col.value('(/HigherTaxValueBands)[1]','Varchar(10)') as [Higher Tax Value Bands]
,Data.Col.value('(/NumberYearsCalculationType)[1]','Varchar(10)') as [Number Years Calculation Type]
,Data.Col.value('(/OnShore)[1]','Varchar(10)') as [OnShore]
,Data.Col.value('(/OffShore)[1]','Varchar(10)') as [OffShore]
FROM #MyXML.nodes('/OffShore') AS Data(Col)
I hope, this is what you are waiting for :)
DECLARE #MyXML XML
SET #MyXML = (SELECT
CAST (
'<AllowAdd>N</AllowAdd>
<Allowed>NUMSEG</Allowed>
<AllSegmentsEqualValue>N</AllSegmentsEqualValue>
<ClusterLevelSA>Y</ClusterLevelSA>
<ClusterLevelPremium>Y</ClusterLevelPremium>
<AllowAssignedAndInTrust>N</AllowAssignedAndInTrust>
<MinSegments>1</MinSegments>
<MaxSegments>100</MaxSegments>
<DefaultSegments>10</DefaultSegments>
<RoundPremiumsTo>2</RoundPremiumsTo>
<TaxDeferredAllowance>0.05</TaxDeferredAllowance>
<HigherTaxValueBands>HTVB</HigherTaxValueBands>
<NumberYearsCalculationType>NONFIN</NumberYearsCalculationType>
<OnShore>POLICY</OnShore>
<OffShore>NONFIN</OffShore>'as XML) as x)
DECLARE #Output nvarchar(max) = N''
DECLARE #PivotList nvarchar(max)
SELECT
#PivotList = COALESCE(#PivotList + ', ', N'') + N'[' + XC.value('local-name(.)', 'varchar(100)') + N']'
FROM
#MyXML.nodes('/*') AS XT(XC)
SET #Output = N'SELECT
'+#PivotList+N'
FROM
(
SELECT
ColName = XC.value(''local-name(.)'', ''nvarchar(100)''),
ColValue = ISNULL(NULLIF(CONVERT(nvarchar(max),XC.query(''./*'')),''''),XC.value(''.'',''nvarchar(max)''))
FROM
#MyXML.nodes(''/*'') AS XT(XC)
) AS s
PIVOT
(
MAX(ColValue)
FOR ColName IN ('+#PivotList+N')
) AS t;'
EXEC sp_executesql #Output, N'#MyXml xml', #MyXML = #MyXML;
Given your input XML, you can try to use this:
SELECT
ColName = XC.value('local-name(.)', 'varchar(100)'),
ColValue = xc.value('(.)[1]', 'varchar(100)')
FROM
#MyXML.nodes('/*') AS XT(XC)
This will output each XML element found under the root - its name and value - as a list:
Of course, since it's a very generic approach, you cannot really define the proper datatypes for each columns in the second xc.value() - you basically get everything as a string.
Using a cursor to build a SELECT statement and sp_executesql to execute it!
DECLARE #MyXML XML
DECLARE #FieldName VARCHAR(MAX)
DECLARE #SELECT_TEXT NVARCHAR(MAX)
SET #MyXML = (SELECT
CAST (
'<AllowAdd>N</AllowAdd>
<Allowed>NUMSEG</Allowed>
<AllSegmentsEqualValue>N</AllSegmentsEqualValue>
<ClusterLevelSA>Y</ClusterLevelSA>
<ClusterLevelPremium>Y</ClusterLevelPremium>
<AllowAssignedAndInTrust>N</AllowAssignedAndInTrust>
<MinSegments>1</MinSegments>
<MaxSegments>100</MaxSegments>
<DefaultSegments>10</DefaultSegments>
<RoundPremiumsTo>2</RoundPremiumsTo>
<TaxDeferredAllowance>0.05</TaxDeferredAllowance>
<HigherTaxValueBands>HTVB</HigherTaxValueBands>
<NumberYearsCalculationType>NONFIN</NumberYearsCalculationType>
<OnShore>POLICY</OnShore>
<OffShore>NONFIN</OffShore>
'
as XML) as x)
SET #SELECT_TEXT = 'SELECT '
DECLARE xml_cursor CURSOR
FOR SELECT Data.Col.value( 'fn:local-name(.)', 'VARCHAR(MAX)' ) FROM #MyXML.nodes('/*') AS Data(Col)
open xml_cursor
While 1 = 1
BEGIN
fetch next from xml_cursor into #FieldName
if ##fetch_status <> 0
begin
break
end
SET #SELECT_TEXT = #SELECT_TEXT + 'Data.Col.value(''(/' + #FieldName + ')[1]'',''Varchar(MAX)'') as [' + #FieldName + ']' + ', '
END
close xml_cursor
deallocate xml_cursor
SET #SELECT_TEXT = SUBSTRING( #SELECT_TEXT,0, LEN(#SELECT_TEXT) ) + 'FROM #MyXML.nodes(''/OffShore'') AS Data(Col)'
EXECUTE sp_executesql #SELECT_TEXT, N'#MyXML XML', #MyXML = #MyXML
As previously mentioned in another comment the drawback is not having column types but it's for a report style query so strings is ok.

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.

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.

Group and concatenate many rows to one

I want to "concatenate" all the "Text"-rows into one single row and get one row as a result. Is this even possible? I use MSSQL Server 2005.
Use FOR XML PATH:
SELECT [Text]+' ' AS 'text()' FROM _table FOR XML PATH('')
Another option - use string concatenation:
DECLARE #s nvarchar(max)
SELECT #s = ISNULL(#s, '') + t + ' ' FROM _table OPTION (MAXDOP 1)
SELECT #s
Please note that the latter one isn't guaranteed to work, afaik, officially the behaviour of "#s = #s + ..." for multi-row resultset is undefined.
MAXDOP 1 hint is used here to prevent the optimizer from creating a parralel execution plan, as this will yield an incorrect result for sure.
I believe you're looking for something like this:
DECLARE #string nvarchar(max)
SET #string = N''
SELECT #string = #string + [Text] + N' ' FROM [YourTable]
SELECT #string
This will concatenate all of the values for the [Text] column into a single variable. You can then select the variable to retrieve all of the values in a single row.
Something like:
DECLARE #result varchar(max)
SELECT #result = COALESCE(#result + ' ','') +[Text] FROM [Table]
SELECT #result