Reputation: 393
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
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
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
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