patternsqlMinor
Passing comma separated values to parameters in stored procedure
Viewed 0 times
storedcommapassingprocedureseparatedvaluesparameters
Problem
I have a requirement to retrieve data from a table based on the comma separated values sent in the parameters for stored procedure. As of now I have made the code work for one single value not sure how to make it work for multiple values.
Sample table:
Test data:
Stored procedure:
Execution script for single value (it works till here):
Now how to make it return appropriate result even when we pass multiple values to the parameter?
Something like this:
The underlying table would minimum have 100000 records at any given point in time.
Did tried reading "Arrays and Lists in SQL Server 2008" by Erland Sommarskog but i
Sample table:
CREATE TABLE [dbo].FinalStatus
(
[ID] [int] Primary key IDENTITY(1,1) NOT NULL,
[Col1] [varchar](15) NULL,
[Col2] [varchar](15) NULL,
[Col3] [varchar](100) NOT NULL,
[LastUpdatedDate] [datetime] NOT NULL DEFAULT (getdate())
)Test data:
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg2')
Insert into FinalStatus (Col1, Col2, Col3) values ('11','C21','Some Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('12','BC21','Some Msg2')Stored procedure:
CREATE PROCEDURE [dbo].[FindResult]
(@col1 VARCHAR(15) = NULL,
@col2 VARCHAR(15) = NULL)
AS
SET NOCOUNT ON
BEGIN
DECLARE @c1 VARCHAR(15)
DECLARE @c2 VARCHAR(15)
SET @c1 = @col1
SET @c2 = @col2
SELECT
Col2, Col1,
LastUpdatedDate, Col3
FROM
dbo.FinalStatus
WHERE
(Col1 = @c1 OR @c1 IS NULL)
AND (Col2 = @c2 OR @c2 IS NULL)
ORDER BY
LastUpdatedDate DESC
ENDExecution script for single value (it works till here):
--To get all data
EXEC [dbo].[FindResult]
--passing first parameter alone
EXEC [dbo].[FindResult] @col1 = '10', @col2 = NULL
--passing second parameter alone
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'c21'Now how to make it return appropriate result even when we pass multiple values to the parameter?
Something like this:
EXEC [dbo].[FindResult] @col1 = '10,12', @col2 = NULL
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'ABC21, c21'The underlying table would minimum have 100000 records at any given point in time.
Did tried reading "Arrays and Lists in SQL Server 2008" by Erland Sommarskog but i
Solution
There are several ways of doing it. Changing the data model may also be a better option if you can do it.
If you want to stick with the comma separated string, you can find many string split functions online using CLR, CTE, XML, ... I am not going to put them all here and I won't benchmark them. But you must know that they all have their own issues. For more information you can look at this post from Aaron Bertrand: Split strings the right way – or the next best way
Sample query (using XML conversion):
Output:
Main query:
The idea is to convert it to a table. It can then be used in a correlated sub query:
Only the
Output:
If you can change the parameter' types, the XML data type can be used. It can be easily deserialized by the procedure and the query.
This query is pretty similar to the previous one. However it gives a better control over invalid values and special characters since there is no conversion or search and replace.
The parameter' types have to be changed to xml. I used only one variable with
The correct path and variables must be updated in the
Using .Net, Powershell or other languages, an array or other types can easily be converted to xml and used as a parameter of the procedure.
...
Another option would be to create a Table Value Type that can be used by the Stored Procedure parameters.
Table Type
Stored Procedure with Table Type parameter(s)
You then update the Stored Procedure using this newly created type:
Stored Procedure call with Table Type parameter(s)
With SQL, you can call it the same way using a table variable:
Output:
Please note that if you are planning on using more than a couple rows for each parameters, you should run some performance test with real data and make sure it work
- Comma separated parameters
If you want to stick with the comma separated string, you can find many string split functions online using CLR, CTE, XML, ... I am not going to put them all here and I won't benchmark them. But you must know that they all have their own issues. For more information you can look at this post from Aaron Bertrand: Split strings the right way – or the next best way
Sample query (using XML conversion):
DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @delimiter nvarchar(1) = N',';
SELECT v1 = LTRIM(RTRIM(vals.node.value('(./text())[1]', 'nvarchar(4000)')))
FROM (
SELECT x = CAST('' + REPLACE(@c1, @delimiter, '') + '' AS XML).query('.')
) v
CROSS APPLY x.nodes('/root/data') vals(node);Output:
v1
10
15
13
14Main query:
The idea is to convert it to a table. It can then be used in a correlated sub query:
DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @c2 nvarchar(100) = N'C21, B21';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT CAST('' + REPLACE(@c1, @delimiter, '') + '' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT CAST('' + REPLACE(@c2, @delimiter, '') + '' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);Only the
WHERE clause has to be updated in your procedure. Parameters remains of varchar type with comma separated values.Output:
ID Col1 Col2 Col3 LastUpdatedDate
1 10 ABC21 Msg1 2016-07-20 09:06:19.380 => match c1
2 10 ABC21 Msg2 2016-07-20 09:06:19.390 => match c1
3 11 C21 Some Msg1 2016-07-20 09:06:19.390 => match c2- XML parameter(s)
If you can change the parameter' types, the XML data type can be used. It can be easily deserialized by the procedure and the query.
This query is pretty similar to the previous one. However it gives a better control over invalid values and special characters since there is no conversion or search and replace.
DECLARE @c xml = N'
10151314
C21B21
';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c1') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c2') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);The parameter' types have to be changed to xml. I used only one variable with
c1 and c2 nodes but 1 variable for each node (c1, c2, ...) and different node names can also be used:DECLARE @c1 xml = N'10151314';
DECLARE @c2 xml = N'C21B21';
DECLARE @c3 xml = N'...';The correct path and variables must be updated in the
nodes part of the CROSS APPLY.Using .Net, Powershell or other languages, an array or other types can easily be converted to xml and used as a parameter of the procedure.
...
- User-Defined Table Types
Another option would be to create a Table Value Type that can be used by the Stored Procedure parameters.
Table Type
CREATE TYPE [dbo].[TableTypeCols] AS TABLE
(
[col] varchar(15)
);Stored Procedure with Table Type parameter(s)
You then update the Stored Procedure using this newly created type:
CREATE OR ALTER PROCEDURE [dbo].[FindResult]
@c1 [dbo].[TableTypeCols] READONLY
, @c2 [dbo].[TableTypeCols] READONLY
AS
SELECT * -- fs.[...], fs.[...], ...
FROM [dbo].[FinalStatus] fs
WHERE
EXISTS (
SELECT 1 FROM @c1 c1 WHERE c1.col = fs.Col1
)
OR EXISTS (
SELECT 1 FROM @c2 c2 WHERE c2.col = fs.Col2
);
GO;READONLY is mandatory in the parameter declaration.Stored Procedure call with Table Type parameter(s)
With SQL, you can call it the same way using a table variable:
DECLARE @tc1 [dbo].[TableTypeCols];
DECLARE @tc2 [dbo].[TableTypeCols];
INSERT INTO @tc1(col) VALUES('10'), ('15'), ('13'), ('14');
INSERT INTO @tc2(col) VALUES('C21'), ('B21');
EXEC dbo.FindResult @c1 = @tc1, @c2 = @tc2;Output:
ID Col1 Col2 Col3 LastUpdatedDate
1 10 ABC21 Msg1 2016-07-20 09:06:19.380 => match c1
2 10 ABC21 Msg2 2016-07-20 09:06:19.390 => match c1
3 11 C21 Some Msg1 2016-07-20 09:06:19.390 => match c2Please note that if you are planning on using more than a couple rows for each parameters, you should run some performance test with real data and make sure it work
Code Snippets
DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @delimiter nvarchar(1) = N',';
SELECT v1 = LTRIM(RTRIM(vals.node.value('(./text())[1]', 'nvarchar(4000)')))
FROM (
SELECT x = CAST('<root><data>' + REPLACE(@c1, @delimiter, '</data><data>') + '</data></root>' AS XML).query('.')
) v
CROSS APPLY x.nodes('/root/data') vals(node);v1
10
15
13
14DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @c2 nvarchar(100) = N'C21, B21';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT CAST('<root><data>' + REPLACE(@c1, @delimiter, '</data><data>') + '</data></root>' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT CAST('<root><data>' + REPLACE(@c2, @delimiter, '</data><data>') + '</data></root>' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);ID Col1 Col2 Col3 LastUpdatedDate
1 10 ABC21 Msg1 2016-07-20 09:06:19.380 => match c1
2 10 ABC21 Msg2 2016-07-20 09:06:19.390 => match c1
3 11 C21 Some Msg1 2016-07-20 09:06:19.390 => match c2DECLARE @c xml = N'
<root>
<c1>10</c1><c1>15</c1><c1>13</c1><c1>14</c1>
<c2>C21</c2><c2>B21</c2>
</root>';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c1') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c2') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);Context
StackExchange Database Administrators Q#144352, answer score: 7
Revisions (0)
No revisions yet.