Search code examples
sqlsql-serverpivotunpivot

SQL Query & unpivot Syntax


I have a permission system that defines users access to certain items.

I need a report page to show members and their access to the items.

The table needs to be like this:

+---------+--------+--------+------+--------+
| Members | Item 1 | Item 2 | .... | Item N |
+---------+--------+--------+------+--------+
| Member1 |   x    |    +   | .... |   +    |
+---------+--------+--------+------+--------+
| Member2 |   +    |    x   | .... |   +    |
+---------+--------+--------+------+--------+

I have a 3 table joined query for listing the members and the items they can access but I could not convert it into pivot syntax.

Select members.id memberID, members.name memberName, items.name as item
From members
Left Join member_permission On memberID = members.id
Left Join items On items.id = member_permission.itemID

This is the not working query I have:

Select memberName, itemName
From (
    select members.id, members.name, item.name as itemName
    from members
    Left Join member_permission On memberID = members.id
    Left Join item On item.id = member_permission.itemID
) p
Unpivot
(itemName For name IN (item.name)) as unp

and the error I receive:

The column name "name" specified in the UNPIVOT operator conflicts with the existing column name in the UNPIVOT argument.

I created an example to play with Sqlfiddle


Solution

  • You don't need to use UNPIVOT for this query. UNPIVOT is used to convert multiple columns into multiple rows. You only need to apply the PIVOT function to turn your items into columns.

    I would first suggest using a windowing function like row_number() to create your new column headers, then apply the PIVOT function:

    select id, name, Item1, Item2, Item3
    from
    (
      select members.id, members.name, items.name as item,
        'item'+
          cast(row_number() over(partition by members.id 
                                 order by members.id) as varchar(10)) col
      from members
      Left Join member_permission 
        On memberID = members.id
      Left Join items 
        On items.id = member_permission.itemID
    ) d
    pivot
    (
      max(item)
      for col in (Item1, Item2, Item3)
    ) piv;
    

    See SQL Fiddle with Demo. Then if you have an unknown number of values your query would need to use dynamic SQL:

    DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX)
    
    select @cols = STUFF((SELECT ',' + QUOTENAME('item'+cast(seq as varchar(10))) 
                        from
                        (
                          select row_number() over(partition by memberid
                                                   order by memberid) seq
                          from member_permission
                        ) d
                        group by seq
                        order by seq
                FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'')
    
    set @query = N'SELECT id, name, ' + @cols + N' 
                from 
                (
                  select members.id, members.name, items.name as item,
                    ''item''+
                      cast(row_number() over(partition by members.id 
                                             order by members.id) as varchar(10)) col
                  from members
                  Left Join member_permission 
                    On memberID = members.id
                  Left Join items 
                    On items.id = member_permission.itemID
                ) x
                pivot 
                (
                    max(item)
                    for col in (' + @cols + N')
                ) p '
    
    execute sp_executesql @query;
    

    See SQL Fiddle with Demo