Search code examples
group-byconcatenationsql-server-2014

SQL to group one column of a table, select one value from another column, and concatenate the rest


I had asked this question before (sql to group table of product owners with a column for primary owner and concatenated secondary owners) but it was for MySQL and the answer uses group_concat which isn't available on SQL Server 2014. Since that one was answered and for MySQL I thought I should ask a new one for SQL Server 2014.

I have a table of products with their owners. Each owner is in their own row and has an owner type of either primary or secondary. Not every product has a secondary owner.

I need to get a table grouped by product with the primary owner in one column and all the secondary owners concatenated in a second column. If a product has multiple primary owners it should select the first one and the rest go to the secondary owners. If a product doesn't have a primary owner then it should just select the first/any secondary owner.

This is an input table:

+---------+------------+----------+
| Product | Owner Type |  Owner   |
+---------+------------+----------+
| a       | primary    | one      |
| a       | secondary  | two      |
| a       | secondary  | three    |
| b       | primary    | four     |
| b       | secondary  | five     |
| c       | primary    | six      |
| d       | secondary  | seven    |
| e       | secondary  | eight    |
| e       | secondary  | nine     |
| f       | primary    | ten      |
| f       | primary    | eleven   |
| f       | secondary  | twelve   |
| f       | secondary  | thirteen |
+---------+------------+----------+

The expected result is:

+---------+---------------+--------------------------+
| Product | Primary Owner |     Secondary Owners     |
+---------+---------------+--------------------------+
| a       | one           | two, three               |
| b       | four          | five                     |
| c       | six           |                          |
| d       | seven         |                          |
| e       | eight         | nine                     |
| f       | ten           | eleven, twelve, thirteen |
+---------+---------------+--------------------------+

If you notice, products d and e don't have a primary owner so it picks the first secondary owner and then doesn't include it again in the secondary owner column. Similar for product f that has two primary owner.

I know how to group by product and use FOR XML PATH to concatenate rows/fields. In the group I know how to select the first product where Owner Type is primary. What I cannot figure out is the logic that would be needed to pick the first primary owner and exclude it from the secondary owner column and/or select the first secondary owner if there is no primary owner and exclude it from the secondary owner column.

Any ideas?


Solution

  • With your sample data, I got it to work like this using ROW_NUMBER() and common table expressions to decide the primary and secondary owners.

    WITH PrimaryOwners AS
    (SELECT Product,Owner AS 'Primary Owner'
    FROM (SELECT ROW_NUMBER() OVER(PARTITION BY Product ORDER BY Product) 'RowNumber',
           Product,OwnerType,Owner
            FROM Product) AS tableA
    WHERE RowNumber = 1),
    
    SecondaryOwners AS
    (SELECT Product,Owner AS 'Secondary Owners'
    FROM (SELECT ROW_NUMBER() OVER(PARTITION BY Product ORDER BY Product) 'RowNumber',
           Product,OwnerType,Owner
            FROM Product) AS tableB
    WHERE RowNumber <> 1),
    
    SecondaryOwnersGrouped AS
    (SELECT DISTINCT Product,
            STUFF((SELECT CAST(', ' AS VARCHAR(MAX)) + [Secondary Owners]
                FROM SecondaryOwners a
                WHERE a.Product = b.Product
                for xml path('')
                ), 1, 1, '') AS 'Secondary Owners'
    FROM SecondaryOwners b)
    
    SELECT p.Product,p.[Primary Owner],
           ISNULL(s.[Secondary Owners],'') 'Secondary Owners'
    FROM PrimaryOwners p
         LEFT JOIN SecondaryOwnersGrouped s
            ON p.Product = s.Product
    

    Results:

    +----------+----------------+------------------------ --+
    | Product  | Primary Owner  | Secondary Owners          |
    +----------+----------------+---------------------------+
    |   a      |    one         |     two, three            |
    |   b      |    four        |        five               |
    |   c      |    six         |                           |
    |   d      |    seven       |                           |
    |   e      |    eight       |        nine               |
    |   f      |    ten         | eleven, twelve, thirteen  |
    +----------+----------------+---------------------------+