SQL Server 2008:在多个列上进行旋转

时间:2013-07-08 19:16:40

标签: sql sql-server-2008 pivot

需要SQL Server 2008中的pivot子句的帮助。我有一个包含此信息的表:

我有一个包含9列的表格:IDPeriod_1Period_4作为日期(即2013-04,2013-07等)和Amount_1Amount_4(即30,40等)。我希望从Period_1Period_4的所有不同日期作为列名,然后将Amount_1与您Period_1对应的Amount_2Period_2对应,与Amount_3对应的Period_3和与Amount_4对应的Period_4作为行值。

这是我现在提出的T-SQL:

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

SELECT @cols = STUFF((
        SELECT DISTINCT ',' + QUOTENAME(ans)
        FROM (
            SELECT Period_1 AS ans
            FROM Booking

            UNION

            SELECT Period_2 AS ans
            FROM Booking

            UNION

            SELECT Period_3 AS ans
            FROM Booking

            UNION

            SELECT Period_4 AS ans
            FROM Booking
            ) a
        FOR XML PATH('')
            ,TYPE
        ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')

SET @query = 'SELECT Id ' + @cols + ' from 
         (
            select Id, Period_1, Amount_1, Period_2, Amount_2
            from Booking
         ) x
        pivot 
        (
            max(Amount_1)
            for Period_1 in (' + @cols + ')

        ) p 
(
            max(Amount_2)
            for Period_2 in (' + @cols + ')

        ) p

        '

EXECUTE (@query)

我得到了错误:

  

Msg 265,Level 16,State 1,Line 18
  PIVOT运算符中指定的列名“2013-10”与PIVOT参数中的现有列名冲突。

有没有办法对包含相同值的多个列进行数据透视查询?请写一个关于如何做的回答。

我很感激这方面的任何帮助。提前致谢。

2 个答案:

答案 0 :(得分:3)

您的部分问题是您的数据未规范化,包含Period_1Amount_1Period_2Amount_2等列会使您难以查询数据。我的第一个建议是考虑将表结构修改为类似于以下内容:

create table booking
(
  id int,
  period datetime,
  amount decimal(10, 5)
);

这将允许您为每个ID分配多个句点和金额。还有其他方法可以设计这个,但这可以让您了解如何修复当前结构。

如果您无法修复您的结构,那么我建议将UNPIVOT然后PIVOT应用到您现有的表格。 UNPIVOT会将多列数据转换为多行,然后您可以将PIVOT应用于金额以获得最终结果。

UNPIVOT的基本语法如下。我使用CROSS APPLY和UNION ALL,因为我们需要同时忽略Period和Amount:

select id, 
  convert(varchar(7), period, 120) period,
  amount
from
(
  select id, 
    period_1, period_2, period_3, period_4,
    amount_1, amount_2, amount_3, amount_4
  from booking
) d
cross apply
(
  select period_1, amount_1 union all
  select period_2, amount_2 union all
  select period_3, amount_3 union all
  select period_4, amount_4
) c (period, amount);

SQL Fiddle with demo。这将为您提供以下格式的数据:

| ID |  PERIOD | AMOUNT |
-------------------------
|  1 | 2013-01 |     30 |
|  1 | 2013-04 |     40 |
|  1 | 2013-07 |     50 |
|  1 | 2013-10 |     60 |

数据采用此格式后,您可以将PIVOT函数应用于Period列中的值:

select id,
  [2013-01], [2013-04], [2013-05],
  [2013-07], [2013-08], [2013-10],
  [2013-11]
from
(
  select id, 
    convert(varchar(7), period, 120) period,
    amount
  from
  (
    select id, 
      period_1, period_2, period_3, period_4,
      amount_1, amount_2, amount_3, amount_4
    from booking
  ) d
  cross apply
  (
    select period_1, amount_1 union all
    select period_2, amount_2 union all
    select period_3, amount_3 union all
    select period_4, amount_4
  ) c (period, amount)
) src
pivot
(
  sum(amount)
  for period in ([2013-01], [2013-04], [2013-05],
                 [2013-07], [2013-08], [2013-10],
                 [2013-11])
) piv;

SQL Fiddle with Demo。当然,如果您提前了解价值,上述内容将会很有效。但是,如果不这样做,那么您将需要使用动态SQL来获得结果:

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

select @cols = STUFF((SELECT ',' + QUOTENAME(period) 
                    from 
                    (
                      select convert(varchar(7), period_1, 120) period, period_1 dt 
                      from booking union all
                      select convert(varchar(7), period_2, 120) period, period_2 dt
                      from booking union all
                      select convert(varchar(7), period_3, 120) period, period_3 dt
                      from booking union all
                      select convert(varchar(7), period_4, 120) period, period_4
                      from booking
                    ) d
                    group by period, dt
                    order by dt
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT id, ' + @cols + ' 
            from
            (
              select id, 
                convert(varchar(7), period, 120) period,
                amount
              from
              (
                select id, 
                  period_1, period_2, period_3, period_4,
                  amount_1, amount_2, amount_3, amount_4
                from booking
              ) d
              cross apply
              (
                select period_1, amount_1 union all
                select period_2, amount_2 union all
                select period_3, amount_3 union all
                select period_4, amount_4
              ) c (period, amount)
            ) src
            pivot 
            (
                sum(amount)
                for period in (' + @cols + ')
            ) p '

execute(@query);

SQL Fiddle with Demo。这些查询将得到类似于:

的结果
| ID | 2013-01 | 2013-04 | 2013-05 | 2013-07 | 2013-08 | 2013-10 | 2013-11 |
----------------------------------------------------------------------------
|  1 |     105 |      40 |      86 |      50 |     120 |      60 |      65 |

答案 1 :(得分:0)

我认为您必须在字段名称后附加内容,并为您正在使用的每个值创建一个@cols变量,即:

SELECT @cols1 = STUFF((
        SELECT DISTINCT ',' + QUOTENAME(ans)
        FROM (SELECT '1_'+CAST(Period_1  AS VARCHAR(12)) AS ans
              FROM #test
              UNION
              SELECT '1_'+CAST(Period_2  AS VARCHAR(12)) AS ans
              FROM #test
            ) a
        FOR XML PATH(''),TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1,'')
        ,@cols2 = STUFF((
                SELECT DISTINCT ',' + QUOTENAME(ans)
                FROM (SELECT '2_'+CAST(Period_1 AS VARCHAR(12)) AS ans
                      FROM #test
                      UNION
                      SELECT '2_'+CAST(Period_2 AS VARCHAR(12)) AS ans
                      FROM #test
                    ) a
                FOR XML PATH(''),TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1,'')

然后在旋转时引用相应的变量,你还必须在那里添加字段名称:

(
    max(Amount_1)
    for '1_'+CAST(Period_1 AS VARCHAR(12)) in (' + @cols1 + ')

) p 

可能有更聪明的方法可以做到这一点,但我不知道。