Monday, February 4, 2013

SQL Code Snippet - Concatonating data from a table

This is a bit of code, with my SQL server management server style comments, for concatonating a list of data from a table that shares a common key.  It has two examples.  One using a CTE and the other using a trick that may or may not work in future versions of SQL Server.

This creates input


myid
COMMENT
1
concat
1
concat2
1
concat3
1
test
2
concaxt
2
concaxt2
2
concaxt3
2
testx


And generates the output of:


myid
COMMENT
1
concat, concat2, concat3, test
2
concaxt, concaxt2, concaxt3, testx



/*
This example provides two methods for concatonating records.  The first method is a pure sql method that works with common table expressions.
The second uses a "trick" that can be done with the current versions of microsoft sql to concat records, it is much faster but may not be usable
in future versions.
*/

IF Object_id('tempdb..#tmp') IS NOT NULL
  BEGIN
      DROP TABLE #tmp
  END

CREATE TABLE #tmp
  (
     myid    INT,
     comment VARCHAR(255)
  )

INSERT INTO #tmp
            (myid,
             comment)
SELECT 1,
       'test'
UNION
SELECT 1,
       'concat'
UNION
SELECT 1,
       'concat2'
UNION
SELECT 1,
       'concat3'
UNION
SELECT 2,
       'testx'
UNION
SELECT 2,
       'concaxt'
UNION
SELECT 2,
       'concaxt2'
UNION
SELECT 2,
       'concaxt3'
/* 

Quick example on how to concatonate records with a CTE 
*/

;

WITH rownumsfororder
     AS (
        /*  Sort the  records in the order you want to concatonate them for there id  in this case we use row num!*/
        SELECT myid,
               comment,
               Row_number()
                 OVER (
                   partition BY myid
                   ORDER BY comment ) row_num
         FROM   #tmp),
     concatenations
     AS (
        --Recursively concactonate the records in the order defined above, this makes lots of records and makes this super slow!  I bet 
        -- there is a trick somewhere that can do this faster! 
        SELECT myid,
               CONVERT(NVARCHAR(max), comment) COMMENT,
               row_num
        FROM   rownumsfororder
        WHERE  row_num = 1
         UNION ALL
         SELECT c.myid,
                ( c.comment + ', ' + l.comment ) COMMENT,
                l.row_num
         FROM   concatenations c -- this is a recursion! 
                INNER JOIN rownumsfororder l
                        ON l.myid = c.myid
                           AND l.row_num = c.row_num + 1),
     -- Now we want to sort so that we get the "deepest" of the recursively created records
     -- This "deepest" record will have the highest row number.  Again, something might be doable above to just simply
     -- choose only good records for this, if you know how to make this faster, send this email back with better examples.
     row_numedconcatenations
     AS (SELECT myid,
                comment,
                Row_number()
                  OVER (
                    partition BY myid
                    ORDER BY row_num DESC ) row_num
         FROM   concatenations),
     concatedlist
     AS (
        /* 
        This gets the concatonated comment 
        */

        SELECT *
         FROM   row_numedconcatenations
         WHERE  row_num = 1)
-- The final set you can then join to your main set of data or pipe into another temp table for a later join.
-- Enjoy! 
SELECT *
FROM   concatedlist

--- This is another, much faster, method of doing concatonations.  The only thing this does not have is flexibility 
-- in the type of concats you can do, IE you can do no transformations on the data like you can with the CTE 
-- recursion.  IF you just want to concat the records, but not prune the records in any way, then this is a good way to go.
-- This may not work in future versions.
SELECT x.myid,
       LEFT(x.comments, Len(x.comments) - 1) AS comments
FROM   (SELECT t1.myid,
               (SELECT comment + ','
                FROM   #tmp AS t2
                WHERE  t2.myid = t1.myid
                ORDER  BY comment
                FOR xml path('')) AS comments
        FROM   #tmp AS t1
        GROUP  BY t1.myid) AS x 

No comments:

Post a Comment