SQL - 将列转换为行(使用数据透视或其他方式)?

时间:2014-08-05 13:02:18

标签: sql sql-server

我正在努力重新格式化查询中的答案集。这是来自监测工具的数据,我正在计算月平均值以及线性回归预测。这一切都在其他功能中执行并且工作正常。这些函数都使用我使用的最终数据提供以下两个表。我目前唯一要解决的问题是重新格式化输出以显示比行更多的列。

首先,让我向您提供我正在使用的示例数据使用SQL Server 2008R2

create table finalactual(Target varchar(100), MonthNum int, [Actual] real)
create table finalprojection(Target varchar(100), MonthNum int, [monthname] varchar(20), [Forecast] real)

Insert into finalprojection values ('C:\', 2, 'February', 65609.29)
Insert into finalprojection values ('C:\', 3, 'March',  65850.27)
Insert into finalprojection values ('C:\', 4, 'April',  66091.26)
Insert into finalprojection values ('C:\', 5, 'May',    66332.25)                                     
Insert into finalprojection values ('C:\', 6, 'June',   66573.23)
Insert into finalprojection values ('C:\', 7, 'July',   66814.22)
Insert into finalprojection values ('C:\', 8, 'August', 67055.2)
Insert into finalprojection values ('C:\', 9, 'September',  67296.19)
Insert into finalprojection values ('C:\', 10, 'October',   67537.17)
Insert into finalprojection values ('C:\', 11, 'November',  67778.16)
Insert into finalprojection values ('C:\', 12, 'December',  68019.14)
Insert into finalprojection values ('C:\', 13, 'January',   68260.13)
Insert into finalprojection values ('E:\', 2, 'February', 385251.0)
Insert into finalprojection values ('E:\', 3, 'March',  401171.1)
Insert into finalprojection values ('E:\', 4, 'April',  417091.2)
Insert into finalprojection values ('E:\', 5, 'May',    433011.3)                                     
Insert into finalprojection values ('E:\', 6, 'June',   448937.3)
Insert into finalprojection values ('E:\', 7, 'July',   464851.4)
Insert into finalprojection values ('E:\', 8, 'August', 480771.4)
Insert into finalprojection values ('E:\', 9, 'September',  496691.5)
Insert into finalprojection values ('E:\', 10, 'October',   512611.6)
Insert into finalprojection values ('E:\', 11, 'November',  528531.6)
Insert into finalprojection values ('E:\', 12, 'December',  544451.8)
Insert into finalprojection values ('E:\', 13, 'January',   560371.8)                                      

Insert into finalactual values ('C:\', 2, 62927.88)
Insert into finalactual values ('C:\', 3, 64534.62)
Insert into finalactual values ('C:\', 4, 67215.3)
Insert into finalactual values ('C:\', 5, 70991.05)
Insert into finalactual values ('C:\', 6, 69857.11)
Insert into finalactual values ('C:\', 7, 64440.7)
Insert into finalactual values ('C:\', 8, 64359.08)
Insert into finalactual values ('E:\', 2, 382691.4)                                     
Insert into finalactual values ('E:\', 3, 400543.9)                                     
Insert into finalactual values ('E:\', 4, 418160.4)                                     
Insert into finalactual values ('E:\', 5, 435643.2)                                     
Insert into finalactual values ('E:\', 6, 451213.5)                                     
Insert into finalactual values ('E:\', 7, 466608.2)                                     
Insert into finalactual values ('E:\', 8, 476218.2)       

如果您运行以下查询

select a.target, a.monthnum as 'Month Value', case when a.monthnum <= 12 then 
a.monthnum else a.monthnum-12 end as 'Month Number', a.monthname as [Month],

b.Actual as 'Actual', a.forecast from finalprojection a
left join
finalactual b on a.Target = b.Target AND a.monthnum = b.MonthNum
order by Target ASC, [Month Value] ASC

结果答案集如下所示:

target  Month Value Month Number    Month   Actual      forecast
C:\     2           2              February 62927.88    65609.29
C:\     3           3              March    64534.62    65850.27
C:\     4           4              April    67215.3     66091.26
C:\     5           5              May      70991.05    66332.25
C:\     6           6              June     69857.11    66573.23
C:\     7           7              July     64440.7     66814.22
C:\     8           8              August   64359.08    67055.2
C:\     9           9              September    NULL    67296.19
C:\     10          10             October      NULL    67537.17
C:\     11          11             November     NULL    67778.16
C:\     12          12             December     NULL    68019.14
C:\     13          1              January      NULL    68260.13
E:\     2           2              February 382691.4    385251
E:\     3           3              March    400543.9    401171.1
E:\     4           4              April    418160.4    417091.2
E:\     5           5              May      435643.2    433011.3
E:\     6           6              June     451213.5    448931.3
E:\     7           7              July     466608.2    464851.4
E:\     8           8              August   476218.2    480771.4
E:\     9           9              September    NULL    496691.5
E:\     10          10             October      NULL    512611.6
E:\     11          11             November     NULL    528531.6
E:\     12          12             December     NULL    544451.8
E:\     13          1              January      NULL    560371.8

这很好,因为我有6个月或7个月的实际数据,结合线性回归计算延长了6个月(这就是为什么9月到1月的“空”值)实际上。

我希望输出的内容是这一行:

Target  A/F  Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec   Jan
C:\      A   62927 64534 67215 70991 69857 64440 64359 NULL  NULL  NULL  NULL  NULL
C:\      F   65609 65850 66091 66332 66573 66814 67055 67296 67537 67778 68019 68260

出于空间原因,我已将小数与A / F(意味着实际与预测)缩写。为了简洁起见,我也再次离开E:\驱动器......

可能存在问题的一个问题是,起始月份(在上面的示例中,它是2月)将是动态的。意思是如果查询在9月运行,第一个月将是3月... 10月将4月作为第一个月...依此类推。

我创建了一个SQL小提琴:http://www.sqlfiddle.com/#!3/901c4/1为你们节省了一些时间。

我提前感谢你们的帮助。

与滑动日期需求相关的更新信息:

大家好......所有优秀的解决方案。

让我解释一下我在开始月份的意思。

今天(8月)运行报告需要将结果集的第一个月设置为二月并且结束于2015年1月。这是因为报告可以追溯到历史记录的6个月以收集月度结果。从那里,它延伸(线性回归)超过实际数据另外6个月(预测)。这是我真正需要适应的一个怪癖(我知道......屁股上的痛苦)。

因此,如果此查询将在下个月(9月)运行,则开始月份需要为3月,并在2015年2月结束。如果我在10月份运行报告,则开始月份需要为4月,这将是增加到2015年5月。我希望澄清如何提出月份的滑动规模。

只需添加有关如何使用月份比例的说明: 列“月值”是输出以必要顺序显示的方式。此列将从开始月份开始递增,并继续12个增量。例如: 如果查询在8月份运行(如上面的示例数据),则数据的起始月份为2月(运行日期为“月份值”,8个数据开始于6个月前或“月值”为2个。这将设置命令你看到上面的“月值”开始于答案集2和继续13.生成这些表的部分例程评估“月份值”列并生成“月号”列(如果它是&lt; 12然后使用原始值...如果&gt; 12然后减去12)。这就是为什么“月份值”为13的行显示“月份数”为1,使用datename()转换返回“一月”。 如果要在下个月(9月或10月)运行相同的查询,则答案集中的所有内容都将移1。起始的“月份值”将是3或3月,结束的“月份值”将是14或2月(系统将从超过12的值中减去12以得到2)。在这种情况下,返回的答案集如下所示:

target  Month Value Month Number    Month   Actual      forecast
C:\     3           3              March    62927.88    65609.29
C:\     4           4              April    64534.62    65850.27
C:\     5           5              May      67215.3     66091.26
C:\     6           6              June     70991.05    66332.25
C:\     7           7              July     69857.11    66573.23
C:\     8           8              August   64440.7     66814.22
C:\     9           9              Sept     64359.08    67055.2
C:\     10          10             October      NULL    67296.19
C:\     11          11             November     NULL    67537.17
C:\     12          12             December     NULL    67778.16
C:\     13          1              January      NULL    68019.14
C:\     14          2              February     NULL    68260.13

注意:为了简洁起见,删除了E:\驱动器

旋转的答案应如下所示:

Target  A/F  Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec   Jan   Feb
C:\      A   62927 64534 67215 70991 69857 64440 64359 NULL  NULL  NULL  NULL  NULL
C:\      F   65609 65850 66091 66332 66573 66814 67055 67296 67537 67778 68019 68260

看到输出的人不需要看多年,因为他们知道报告显示了12个月的实际和预测数据......所以当“1月”出现时,他们知道他们正在跨越进入新的一年。

我希望这有助于您了解答案集的结构。

再次......感谢您当前的想法...非常好的东西。

3 个答案:

答案 0 :(得分:7)

虽然其他两个答案可以得到你想要的结果,但我应该做的略有不同。您可以先unpivotActual列中Forecast数据,然后转动月份。

您没有指定正在使用的SQL Server版本,但从SQL Server 2005+开始,您可以使用CROSS APPLY进行取消隐藏。

基本语法类似于:

select a.target, 
  Left(a.monthname, 3) as [Month],
  AorF,
  value
from finalprojection a
left join finalactual b 
  on a.Target = b.Target 
 AND a.monthnum = b.MonthNum
cross apply
(
  select 'A', b.Actual union all
  select 'F', a.forecast
) c(AorF, value);

Demo。这会将您的多列ActualForecast转换为多行。一旦数据采用这种格式,您就可以轻松地调整完整脚本的月份:

select target, 
  AorF,
  Jan, Feb, Mar, Apr, May, Jun, Jul,
  Aug, Sep, Oct, Nov, Dec
from
(
  select a.target, 
    Left(a.monthname, 3) as [Month],
    AorF,
    value
  from finalprojection a
  left join finalactual b 
    on a.Target = b.Target 
    AND a.monthnum = b.MonthNum
  cross apply
  (
    select 'A', b.Actual union all
    select 'F', a.forecast
  ) c(AorF, value)
)  d
pivot
(
  max(value)
  for month in (Jan, Feb, Mar, Apr, May, Jun, Jul,
                Aug, Sep, Oct, Nov, Dec)
) piv
order by target;

SQL Fiddle with Demo。这给出了最终结果:

| TARGET | AORF |           JAN |            FEB |            MAR |           APR |          MAY |           JUN |            JUL |          AUG |        SEP |          OCT |         NOV |          DEC |
|--------|------|---------------|----------------|----------------|---------------|--------------|---------------|----------------|--------------|------------|--------------|-------------|--------------|
|    C:\ |    A |        (null) | 62927.87890625 | 64534.62109375 |  67215.296875 | 70991.046875 |  69857.109375 | 64440.69921875 | 64359.078125 |     (null) |       (null) |      (null) |       (null) |
|    C:\ |    F | 68260.1328125 |  65609.2890625 |  65850.2734375 | 66091.2578125 |     66332.25 | 66573.2265625 |    66814.21875 | 67055.203125 | 67296.1875 | 67537.171875 | 67778.15625 | 68019.140625 |
|    E:\ |    A |        (null) |   382691.40625 |   400543.90625 |  418160.40625 |  435643.1875 |      451213.5 |    466608.1875 |  476218.1875 |     (null) |       (null) |      (null) |       (null) |
|    E:\ |    F |   560371.8125 |         385251 |   401171.09375 |   417091.1875 |  433011.3125 |   448937.3125 |   464851.40625 | 480771.40625 |   496691.5 | 512611.59375 |  528531.625 |  544451.8125 |

如果您需要根据开始月份对列进行不同的排序,那么您必须使用类似于以下的动态SQL:

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

set @start = 8

select @cols = STUFF((SELECT ',' + QUOTENAME(Left(monthname, 3)) 
                    from finalprojection
                    where MonthNum > (@start - 6)
                      and MonthNum <= (@start + 6)
                    group by monthNum,MonthName
                    order by MonthNum
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query = 'SELECT target, AorF,' + @cols + ' 
            from 
            (
              select a.target, 
                Left(a.monthname, 3) as [Month],
                AorF,
                value
              from finalprojection a
              left join finalactual b 
                on a.Target = b.Target 
                AND a.monthnum = b.MonthNum
              cross apply
              (
                select ''A'', b.Actual union all
                select ''F'', a.forecast
              ) c (AorF, value)
            ) x
            pivot 
            (
                max(value)
                for month in (' + @cols + ')
            ) p 
            order by target'

exec sp_executesql @query;

请参阅Demo

答案 1 :(得分:1)

根据运行的月份,我根本不明白列的含义是不同的。如果你能解释我可以帮助找到解决方案。这个查询看起来很冗长,但是一旦你将其分解,它实际上非常简单。这是使用所谓的交叉表。为了打破实际和预测,我分别做了它们,然后使用UNION将它们重新组合在一起。

with MyCte as
(
    select a.target
        , a.monthnum 
        , b.Actual
        , a.forecast 
        , ROW_NUMBER() over(partition by a.target order by a.monthnum) as RowNum
    from finalprojection a
    left join finalactual b on a.Target = b.Target AND a.monthnum = b.MonthNum
)

select Target
    , 'A' as [A/F]
    , max(case when monthnum = 2 then Actual end) as Feb
    , max(case when monthnum = 3 then Actual end) as Mar
    , max(case when monthnum = 4 then Actual end) as Apr
    , max(case when monthnum = 5 then Actual end) as May
    , max(case when monthnum = 6 then Actual end) as Jun
    , max(case when monthnum = 7 then Actual end) as Jul
    , max(case when monthnum = 8 then Actual end) as Aug
    , max(case when monthnum = 9 then Actual end) as Sep
    , max(case when monthnum = 10 then Actual end) as Oct
    , max(case when monthnum = 11 then Actual end) as Nov
    , max(case when monthnum = 12 then Actual end) as Dec
    , max(case when monthnum = 1 then Actual end) as Jan
from MyCte
group by Target

union all

select Target
    , 'F' as [A/F]
    , max(case when monthnum = 2 then Forecast end) as Feb
    , max(case when monthnum = 3 then Forecast end) as Mar
    , max(case when monthnum = 4 then Forecast end) as Apr
    , max(case when monthnum = 5 then Forecast end) as May
    , max(case when monthnum = 6 then Forecast end) as Jun
    , max(case when monthnum = 7 then Forecast end) as Jul
    , max(case when monthnum = 8 then Forecast end) as Aug
    , max(case when monthnum = 9 then Forecast end) as Sep
    , max(case when monthnum = 10 then Forecast end) as Oct
    , max(case when monthnum = 11 then Forecast end) as Nov
    , max(case when monthnum = 12 then Forecast end) as Dec
    , max(case when monthnum = 1 then Forecast end) as Jan
from MyCte
group by Target
order by Target, [A/F]

答案 2 :(得分:1)

以下是基于您提供结果的查询的数据透视表。它分别进行实际和预测。要使列具有不同的顺序,您需要以不同的顺序选择它们。

SELECT Target, 'Actual' AS [A/F]
    , [February], [March], [April], [May], [June], [July], [August]
    , [September], [October], [November], [December], [January]
FROM ( 
        SELECT [Target], [Month], [Actual] FROM (
            select a.target, a.monthnum as 'Month Value', case when a.monthnum <= 12 then 
            a.monthnum else a.monthnum-12 end as 'Month Number', a.monthname as [Month],
            b.Actual as 'Actual', a.forecast 
            from @finalprojection a left join @finalactual b on a.Target = b.Target AND a.monthnum = b.MonthNum
         ) AS YourTable 
     ) AS SourceTable
PIVOT (
         SUM([Actual]) FOR [Month] IN ([February], [March], [April], [May], [June], [July], [August]
                                     , [September], [October], [November], [December], [January])
       ) AS PivotData
UNION ALL 
SELECT Target, 'Forecast' AS [A/F]
    , [February], [March], [April], [May], [June], [July], [August]
    , [September], [October], [November], [December], [January]
FROM ( 
        SELECT [Target], [Month], [forecast] FROM (
            select a.target, a.monthnum as 'Month Value', case when a.monthnum <= 12 then 
            a.monthnum else a.monthnum-12 end as 'Month Number', a.monthname as [Month],
            b.Actual as 'Actual', a.forecast 
            from @finalprojection a left join @finalactual b on a.Target = b.Target AND a.monthnum = b.MonthNum
            ) AS YourTable 
        ) AS SourceTable
PIVOT (
          SUM([forecast]) FOR [Month] IN ([February], [March], [April], [May], [June], [July], [August]
                                        , [September], [October], [November], [December], [January])
      ) AS PivotData

当我使用您提供的数据运行时,我得到以下内容:

Target  A/F         February        March           April           May             June            July            August          September   October         November    December        January
C:\     Actual      62927.87890625  64534.62109375  67215.296875    70991.046875    69857.109375    64440.69921875  64359.078125    NULL        NULL            NULL        NULL            NULL
E:\     Actual      382691.40625    400543.90625    418160.40625    435643.1875     451213.5        466608.1875     476218.1875     NULL        NULL            NULL        NULL            NULL
C:\     Forecast    65609.2890625   65850.2734375   66091.2578125   66332.25        66573.2265625   66814.21875     67055.203125    67296.1875  67537.171875    67778.15625 68019.140625    68260.1328125
E:\     Forecast    385251          401171.09375    417091.1875     433011.3125     448937.3125     464851.40625    480771.40625    496691.5    512611.59375    528531.625  544451.8125     560371.8125       

请注意,数据透视表使用了所有数据的总和 - 如果您为每个目标设置了多个条目,那么可能会给您带来狡猾的结果。月。这是因为枢轴需要分组。这可能是您的月份顺序问题出现的地方,但我需要您更具体地确定。