One of the frequently asked questions in a lot of SQL Server forums is how to handle a comma-delimited value passed as a parameter in a stored procedure.
To better illustrate the question, let’s say you have a website wherein you have a page that lets the users select data items which are sent back to the database for further processing in variety of formats, and of all the formats, comma separated is probably the format which developers struggle with most if they do not know how to consume this structure.
Databases do not understand data arranged horizontally, so how to convert the data from CSV (horizontal) to a table (vertical) ?
Fortunately there are a couple of tricks up database’s sleeve.
1. Dynamic SQL Approach
This method involves creating a SQL statement during execution time to evaluate the where condition. But this approach is as ugly as it sounds nasty. Creating SQL statments on the the fly would results in lot of activity in shared pool. i.e. Compilation of statement, execution plan generation and final execution of query. All of these consume precious server resources and hence should avoided if at all possible.
Due to the obvious pit-falls in this approach I am not even going to attempt to show how-to.
2. Common Table Expression (CTE) based Approach
Comman table expressions (CTE) is probably one of the most powerful programming construct introduced in the SQL Server 2005. It can be used in hierarchical queries, special aggregations and various other problem solving. Various uses of CTE are discussed elsewhere in my blog, which can be found using the search button on the blog homepage.
Below is a CTE based implementation. The good aspect about this approach is that it is completely object independent so the function can be used for any table in any schema of the database as long as you have sufficient permissions.
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N‘[dbo].[ufnCSVToTable]’)
AND type in (N‘FN’, N‘IF’, N‘TF’, N‘FS’, N‘FT’))
DROP FUNCTION [dbo].[ufnCSVToTable]
CREATE FUNCTION [dbo].[ufnCSVToTable](@str VARCHAR(4000), @sep CHAR(1))
RETURNS @table TABLE
( [colx] VARCHAR(4000) NULL )
WITH r0 AS
SELECT 1 n,1 m
SELECT n+1, m+ CASE WHEN SUBSTRING(@str,n,1)=@sep THEN 1 ELSE 0 END
FROM r0 WHERE n<=LEN(@str)
INSERT INTO @table
SELECT SUBSTRING(@str,MIN(n), MAX(n)-MIN(n)) colx
FROM r0 GROUP BY m;
SELECT * FROM [dbo].[ufnCSVToTable](‘this,is,a,comma,separated,sentence‘,‘,’)
–Usage with table
DECLARE @str VARCHAR(1000); SET @str = ‘1,2’;
FROM Person.Address Ad
INNER JOIN [dbo].[ufnCSVToTable](@str,‘,’) CSV ON Ad.AddressID = CSV.colx
I hope you find this function useful.