WorkInProgress
WorkInProgress

Reputation: 393

Sql Server 2008 Row to Column

I have two tables, tempUsers and tempItems. These two tables have a one to many relationship.

When I use an inner join on these two tables the result looks like this:

**user | Category**
Jack | Shoes
Jack | Tie
Jack | Glass
Peggy | Shoe
Peggy | Skirt
Peggy | Bat
Peggy | Cat
Bruce | Laptop
Bruce | Beer
Chuck | Cell Phone

I would instead like a result that looks like this:

**User | Category1  | Category2 | Category3 | Category4**
Jack   | Shoes      | Tie       | Glass     | .....
Peggy  | Shoe       | Skirt     | Bat       | Cat
Bruce  | Laptop     | Beer      |.....      |......
Chuck  | Cell Phone | .....     |.......    |

The number of distinct categories in the category is dynamic - there can be any number of them for a given item.

How can I produce this result?

Upvotes: 3

Views: 1955

Answers (3)

WorkInProgress
WorkInProgress

Reputation: 393

Here is the solution using multiple tables. This solution is entirely based on bluefeet's solution. I have just added user id.

create table #tmpUsers
(user_id int, user_name varchar(255));
insert into #tmpUsers values (1,'Jack');
insert into #tmpUsers values (2,'Peggy');
insert into #tmpUsers values (3,'Bruce');
insert into #tmpUsers values (4,'Chuck');


create table #tmpItems
(user_id int, category varchar(255));
insert into #tmpItems values(1,'Shoes');
insert into #tmpItems values(1,'Tie');
insert into #tmpItems values(1,'Glass');

insert into #tmpItems values(2,'Shoe');
insert into #tmpItems values(2,'Skirt');
insert into #tmpItems values(2,'Bat');
insert into #tmpItems values(2,'Cat');

insert into #tmpItems values(3,'Laptop');
insert into #tmpItems values(3,'Beer');

insert into #tmpItems values(4,'Cell Phone');


select TU.user_name,TI.category from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id


DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME('Category'+cast(row_number() over(partition by TU.[user_id] 
                                                                      order by TU.[user_id]) as varchar(3))) 
                    from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')



set @query = 'SELECT [user_name],' + @cols + ' 
              from
              (
                select TU.[user_name], TI.category,
                  ''Category''+cast(row_number() over(partition by TU.[user_id] 
                                                    order by TU.[user_id] ) as varchar(3)) rn
                from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
              ) d
              pivot 
              (
                  max(category)
                  for rn in (' + @cols + ')
              ) p '

execute(@query)


drop table #tmpUsers
drop table #tmpItems

Upvotes: 0

Joel Coehoorn
Joel Coehoorn

Reputation: 415690

Sql Server does allow you to pivot data. However, like other relational database, it still requires that you know at the outset of a query how many columns (and of what type) the results will be, even with the PIVOT. The best you can hope for here is to use queries, combined with dynamic sql (building the query string in code at runtime), to first find out who has the most categories, and then build a query that PIVOTs your data to look for that many items.

The normal solution to pivoting with an unknown number of columns is do the pivot client side, from the code that calls into the server.

Upvotes: 0

Taryn
Taryn

Reputation: 247670

There are a few ways that you can transform the data from rows into columns.

Since you are using SQL Server 2008, then you can use the PIVOT function.

I would suggest using the row_number() function to assist in pivoting the data. If you have a known number of values, then you could hard-code the query:

select user, category1, category2, category3, category4
from
(
  select [user], category,
    'Category'+cast(row_number() over(partition by [user] 
                                      order by [user]) as varchar(3)) rn
  from yt
) d
pivot
(
  max(category)
  for rn in (category1, category2, category3, category4)
) piv;

See SQL Fiddle with Demo.

For your situation you stated that you will have an unknown number of values that need to be columns. In that case, you will want to use dynamic SQL to generate the query string to execute:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME('Category'+cast(row_number() over(partition by [user] 
                                                                      order by [user]) as varchar(3))) 
                    from yt
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT [user],' + @cols + ' 
              from
              (
                select [user], category,
                  ''Category''+cast(row_number() over(partition by [user] 
                                                    order by [user]) as varchar(3)) rn
                from yt
              ) d
              pivot 
              (
                  max(category)
                  for rn in (' + @cols + ')
              ) p '

execute(@query)

See SQL Fiddle with Demo. Both give a result:

|  USER |  CATEGORY1 | CATEGORY2 | CATEGORY3 | CATEGORY4 |
----------------------------------------------------------
| Bruce |     Laptop |      Beer |    (null) |    (null) |
| Chuck | Cell Phone |    (null) |    (null) |    (null) |
|  Jack |      Shoes |       Tie |     Glass |    (null) |
| Peggy |       Shoe |     Skirt |       Bat |       Cat |

Upvotes: 2

Related Questions