How to Pivot multiple rows of data into a comma delimited list/string using COALESCE

At some point you might need to turn multiple rows from a column into a delimited list. I think I saw this done for the first time about 3 years ago, and I clearly remember having to do a double take at the code upon seeing it. It’s been in my sql-toolbox since then, though admittedly it doesn’t see much action. At any rate I thought I’d share, so let’s set up a scenario to walk through. Let’s say we have a table called ‘Numbers’ that has a column named ‘ID’ that contains integers. If we ran the following query:

SELECT * FROM Numbers

We would get the following results:

clip_image001[4]

The table basically contains the values 1-6 with the value 5 appearing twice. What we want to achieve is to get our values to appear as one delimited (comma or otherwise) string like so:

‘1, 2, 3, 4, 5, 6’

We can use the COALESCE function in combination with a variable to achieve the desired results using the code below, just make sure to change the table and column name to suit your specific scenario.

--declare local variables

DECLARE

      @v_DelimitedString VARCHAR(MAX)

--pivot rows into delimited string

SELECT

      @v_DelimitedString = COALESCE(@v_DelimitedString, '') + CONVERT(VARCHAR(10), ID) + ','

FROM

      Numbers

     

--trim off last comma

SET @v_DelimitedString = (SELECT SUBSTRING(@v_DelimitedString, 1, LEN(@v_DelimitedString)-1))

--show results   

SELECT @v_DelimitedString

Running this code against our Numbers table would produce the following results.

clip_image002[4]

That’s almost what we’re looking for, although we’re seeing the value ‘5’ repeated multiple times since it appears multiple times in our data. This problem is easy enough to solve though, by replacing our ‘Numbers’ table with a dynamic SELECT statement like so (see green highlight for changed code below)

--declare local variables

DECLARE

      @v_DelimitedString VARCHAR(MAX)

--pivot rows into delimited string

SELECT

      @v_DelimitedString = COALESCE(@v_DelimitedString, '') + CONVERT(VARCHAR(10), ID) + ','

FROM

      (SELECT DISTINCT ID FROM Numbers) Nbrs

     

--trim off last comma

SET @v_DelimitedString = (SELECT SUBSTRING(@v_DelimitedString, 1, LEN(@v_DelimitedString)-1))

--show results   

SELECT @v_DelimitedString

Now our repeating 5 value has been eliminated and our results are being returned as so:

clip_image003[4]

It’s a pretty nifty trick that was a little more useful before table value parameters were available. It can still come in handy if you wanted to do something like use the T-SQL PIVOT function to return a dynamic set of columns. I’ll be covering how to do that in my next post. Hope this helps.

 

How To Create Optional Parameters In A Stored Procedure Using T-SQL

Optional parameters are a pretty critical part of stored procedure writing. You won't get too far into report writing before users start asking for filters on their reports. In general it's a good idea to send as little data "across the wire" as possible for performance reasons (although you can filter data sets in Reporting Services after the fact as well). So how do you add an optional parameter to a stored procedure? Well, let's set up some example data. I have a table called "VideoGames" that has the following data in it.

clip_image001

Now let's talk about our objective: We want to write a procedure that is capable of returning the "GameTitle" and "GameSystem" columns in our "VideoGame" table. We also want our procedure to have an optional parameter called @p_GameSystem that returns only matching results for a Game System if a value is passed into it. If nothing gets passed into @p_GameSystem we want no filter to be applied to our procedure.

Ok let's start with a simple procedure that does almost everything we want it to, but the @p_GameSystem is not optional, and must always passed in when calling the procedure.

CREATE PROCEDURE usp_OpParamExample

@p_GameSystem VARCHAR(50)

AS

BEGIN --proc

      SELECT

            vg.GameTitle,

            vg.GameSystem

      FROM

            VideoGames vg

      WHERE

            vg.GameSystem = @p_GameSystem      

END --proc 

The first thing we need to do in order to make our @p_GameSystem parameter optional is to give it a default value. We can accomplish that by simply putting an = NULL after its declaration like so:

@p_GameSystem VARCHAR(50) = NULL

The next thing well want to do is change the WHERE clause to behave differently based on the value of @p_GameSystem . To do that, we're going to change its comparison to the following:

      WHERE

            (@p_GameSystem IS NULL OR vg.GameSystem = @p_GameSystem)   

Let's examine how this statement will evaluate for each of its two possible scenarios:

1. Nothing is passed into @p_GameSystem when the procedure is called: Under this scenario the first part of our WHERE statement (@p_GameSystem IS NULL) will always evaluate to true. Because we used "OR" between our two conditionals, only one of the statements needs to be true in order for the row to be returned. So every row in our table will be returned.

2. Something is passed into @p_GameSystem when the procedure is called: Under this scenario, the first test (@p_GameSystem IS NULL) will always be false, however our second condition (vg.GameSystem = @p_GameSystem) will potentially be true, assuming we've passed in a value that exists in our table. Because we used "OR" between our two conditionals, only one of the statements needs to be true in order for the row to be returned. So only rows where (vg.GameSystem = @p_GameSystem) will be returned.

That's it! We now have a procedure that allows a parameter to be optionally passed in. The entire procedure would now look like this:

CREATE PROCEDURE usp_OpParamExample

@p_GameSystem VARCHAR(50) = NULL

AS

BEGIN --proc

      SELECT

            vg.GameTitle,

            vg.GameSystem

      FROM

            VideoGames vg

      WHERE

            (@p_GameSystem IS NULL OR vg.GameSystem = @p_GameSystem)   

END --proc 

Now there are a few other ways we could have implemented this. Two common alternates to the WHERE statement would be either using the ISNULL operator like so:

WHERE

      vg.GameSystem = ISNULL(@p_GameSystem, vg.GameSystem)

or using COALESCE like so:

WHERE

      vg.GameSystem = COALESCE(@p_GameSystem, vg.GameSystem)

While I must admit both of these alternatives are way easier to read, It's my experience that neither of them perform as well as the method we used in our example, particularly against large sets, or where we have multiple optional parameters in the same query. Hope this helps.