Sql Server 2008 строка в столбец


У меня есть две таблицы, tempUsers и tempItems. Эти две таблицы имеют отношение один ко многим.

Когда я использую внутреннее соединение для этих двух таблиц, результат выглядит следующим образом:

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

Вместо этого я хотел бы получить результат, который выглядит следующим образом:

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

Число различных категорий в категории является динамическим - их может быть любое количество для данного элемента.

Как я могу получить этот результат?

3 3

3 ответа:

Существует несколько способов преобразования данных из строк в столбцы.

Так как вы используете SQL Server 2008, то вы можете использовать функцию PIVOT.

Я бы предложил использовать функцию row_number(), чтобы помочь в повороте данных. Если у вас есть известное число значений, то вы можете жестко запрограммировать запрос:

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;

Смотрите SQL Fiddle with Demo.

Для вашей ситуации вы заявили, что у вас будет неизвестное число значений, которые должны быть столбцами. В этом случае вы захотите использовать динамический SQL для создания строки запроса для выполнения:

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)

Смотрите SQL Fiddle with Demo. Оба дают результат:

|  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 |

Sql Server позволяет выполнять сводку данных. Тем не менее, как и другие реляционные базы данных, он по-прежнему требует, чтобы вы знали в начале запроса, сколько столбцов (и какого типа) результаты будут, даже с разворотом. Лучшее, на что вы можете надеяться здесь, - это использовать запросы в сочетании с динамическим sql (построение строки запроса в коде во время выполнения), чтобы сначала выяснить, у кого больше категорий, а затем построить запрос, который сводит ваши данные для поиска такого количества элементов.

Нормальный решение для поворота с неизвестным числом столбцов-это сделать поворот на стороне клиента, из кода, который вызывает сервер.

Вот решение с использованием нескольких таблиц. Это решение полностью основано на решенииbluefeet . Я только что добавил идентификатор пользователя.

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