【问题标题】:Dynamic Columns - SQL Server - Months as Columns动态列 - SQL Server - 以月份为列
【发布时间】:2011-01-15 21:45:38
【问题描述】:

数据库:SQL Server 2005

我们有一个以这种方式包含数据的表:

Project              Year        Jan                   Feb                   Mar                   Apr                   May                   Jun                   Jul                   Aug                   Sep                   Oct                   Nov                   Dec
-------------------- ----------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- ---------------------
11-11079             2008        0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  75244.90
11-11079             2009        466.00                0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00
11-11079             2010        855.00                0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00                  0.00  
01-11052             2009        56131.00              0.00                  36962.00              -61596.00             2428.00               84.00                 0.00                  0.00                  0.00                  0.00                  0.00                  0.00

有人希望将整个项目的数据显示为一行。这些列将是动态的,取决于它进入未来多少年。一个例子是:

Project        Jan-2009     Feb-2009     Mar-2009     Apr-2009... Dec-2009     Jan-2010
-------------- ------------ ------------ ------------ ----------- ------------ ---------
11-11079       466.00       0.00         0.00         0.00        0.00         855.00    
01-11052       56131.00     0.00         36962.00     -61596.00   2428.00      0.00

我阅读了许多示例,其中每个条目的日期都填充在一列中,但我没有发现任何情况下月份是列名而年份在行中。

带有数据透视表的动态 SQL?
或者使用 SQL、临时表、连接和联合进行一些相当广泛的操作?
对使用 SSIS 数据透视表功能有何想法?

【问题讨论】:

  • 请不要在标题或您的问题中使用术语“MS SQL”。没有这样的产品。使用它会使搜索难以找到您的问题,并混淆 SQL Server 和 MySQL。
  • 嘿 - 我忽略了我知道答案的 tsql 问题,因为我将 MSSQL 读为 MySQL
  • John - 这些名字很接近且令人困惑,但为了公平起见,Microsoft SQL Server 以某种方式称为 MSSQL。看看二进制文件的路径:-) C:\Program Files\Microsoft SQL Server\MSSQL10.TEST2008\MSSQL 或 PowerShell > Get-Service,它产生 MSSQL$TEST2008 作为服务的名称,至少对我来说.
  • @onupdatecascade:文件夹名称不等同于产品名称。我也没有责备他们或任何东西 - 我正在解释为什么不在 SO 上使用该术语。
  • 我认为如果有什么你可以争辩的,SQL Server真的 sql 服务器 的糟糕名称 - 不管它可能是恰当的。 MSSQL 肯定更具描述性,并且会排除其他内容(例如此消歧页面上列出的内容:en.wikipedia.org/wiki/SQL_Server)。更不用说它有这种傲慢的权威声音,这并不能很好地代表产品或竞争对手的替代品。

标签: sql sql-server sql-server-2005 tsql ssis


【解决方案1】:

您的数据已经过透视,但需要在不同的级别进行透视。我认为处理此问题的最佳方法是先取消透视,然后再处理正确的透视级别。

第 1 步:反透视

您可以使用 SQL 2005 UNPIVOT 命令,或使用 CROSS JOIN 技术。以下是两者的示例。请注意,为了简单起见,我在中间省略了几个月。只需添加它们。

-- CROSS JOIN method (also works in SQL 2000)
SELECT
   P.Project,
   Mo =
      DateAdd(mm,
         X.MonthNum,
         DateAdd(yy, P.[Year] - 1900, '19000101')
      ),
   Amount = 
      CASE X.MonthNum
         WHEN 0 THEN Jan
         WHEN 1 THEN Feb
         WHEN 11 THEN Dec
      END
FROM
   ProjectData P
   CROSS JOIN (
      SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 11
   ) X (MonthNum)

每行重复 12 次,然后 CASE 语句只为每行提取一个月,从而很好地保留数据。

-- UNPIVOT method
SELECT
    P.Project,
    Mo =
       DateAdd(mm,
          Convert(int, P.MonthNum),
          DateAdd(yy, P.[Year] - 1900, '19000101')
       ),
    P.Amount
FROM
   (
      SELECT Project, [Year], [0] = Jan, [1] = Feb, [11] = Dec
      FROM ProjectData
   ) X UNPIVOT (Amount FOR MonthNum IN ([0], [1], [11])) P

DROP TABLE ProjectData

这两种方法在任何时候都不是明显的性能赢家。有时一个比另一个工作得更好(取决于数据被透视)。 UNPIVOT 方法在执行计划中使用了 CROSS JOIN 没有的过滤器。

第 2 步:再次旋转

现在,如何使用未透视数据。您没有说您的 某人 将如何使用它,但由于您需要将数据放在某种输出文件中,我建议使用 SSRS(Sql Server Reporting Services),它附带 SQL Server 2005,无需额外费用。

只需使用Matrix 报表对象来旋转上述查询之一。这个对象很高兴地确定了在报表运行时制作列标签的数据值,听起来正是您所需要的。如果您添加一列完全按照您喜欢的方式格式化日期,那么您可以按 Mo 列排序,但使用新表达式作为列标签。

SSRS 还提供多种格式和调度选项。例如,您可以让它通过电子邮件发送 Excel 文件或将网页保存到文件共享中。

如果我遗漏了什么,请告诉我。

对于任何想要查看上述代码的人来说,这里有一些创建脚本供您使用:

USE tempdb

CREATE TABLE ProjectData (
    Project varchar(10),
    [Year] int,
    Jan decimal(15, 2),
    Feb decimal(15, 2),
    Dec decimal(15, 2)
)

SET NOCOUNT ON

INSERT ProjectData VALUES ('11-11079', 2008, 0.0, 0.0, 75244.90)
INSERT ProjectData VALUES ('11-11079', 2009, 466.0, 0.0, 0.0)
INSERT ProjectData VALUES ('11-11079', 2010, 855.0, 0.0, 0.0)
INSERT ProjectData VALUES ('01-11052', 2009, 56131.0, 0.0, 0.0)

【讨论】:

  • 在发布我最初的问题后,我花了一个下午盯着数据,我突然发现数据已经被旋转了。我在它上面运行一个快速的 unpivot 的那一刻,灯亮了,我能够按照我想要的方式构建它。所以你的技术就是我最终要走的路!
【解决方案2】:

我写了一个名为 pivot_query 的存储过程,可以帮助解决这个问题,来源是 here,原始数据示例 here

With your data:

create table ProjectData
   (
   Project                      varchar(20),
   [Year]                       Integer,
   Jan                          decimal(12,2),
   Feb                          decimal(12,2),
   Mar                          decimal(12,2),
   Apr                          decimal(12,2),
   May                          decimal(12,2),
   Jun                          decimal(12,2),
   Jul                          decimal(12,2),
   Aug                          decimal(12,2),
   Sep                          decimal(12,2),
   Oct                          decimal(12,2),
   Nov                          decimal(12,2),
   Dec                          decimal(12,2)
   );

insert into ProjectData values ('11-11079',2008, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 75244.90);
insert into ProjectData values ('11-11079',2009, 466.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00);
insert into ProjectData values ('11-11079',2010, 855.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00) ;
insert into ProjectData values ('01-11052',2009, 56131.00, 0.00, 36962.00, -61596.00, 2428.00, 84.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00);

declare @mySQL varchar(MAX)

set @mySQL = 'select * from ProjectData'

exec pivot_query @mySQL, 'Project', 'Year', 'max(Jan) Jan,max(Feb) Feb,max(Mar) Mar,max(Apr) Apr,max(Jun) Jun,max(Jul) Jul,max(Aug) Aug,max(Sep) Sep,max(Oct) Oct,max(Nov) Nov,max(Dec) Dec'

Results:
Project              2008_Jan     2008_Feb     2008_Mar     2008_Apr     2008_Jun     2008_Jul     2008_Aug     2008_Sep     2008_Oct     2008_Nov     2008_Dec     2009_Jan     2009_Feb     2009_Mar     2009_Apr     2009_Jun     2009_Jul     2009_Aug     2009_Sep     2009_Oct     2009_Nov     2009_Dec     2010_Jan     2010_Feb     2010_Mar     2010_Apr     2010_Jun     2010_Jul     2010_Aug     2010_Sep     2010_Oct     2010_Nov     2010_Dec
-------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------
01-11052             NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL         56131.00     .00          36962.00     -61596.00    84.00        .00          .00          .00          .00          .00          .00          NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL         NULL
11-11079             .00          .00          .00          .00          .00          .00          .00          .00          .00          .00          75244.90     466.00       .00          .00          .00          .00          .00          .00          .00          .00          .00          .00          855.00       .00          .00          .00          .00          .00          .00          .00          .00          .00          .00

精确,但非常接近。 :-)

【讨论】:

  • 我忘记添加 5 月份了,但是 SO 不允许我编辑帖子以将其放入。:-)
  • 天哪,罗恩。当问题可以通过基于集合的代码和现有的 SQL Server 工具解决时,为什么还要使用大量的过程代码?
  • 它是通用的,适用于任何查询,无需知道您的数据透视列的值。在许多“透视”样式报告中为我们节省了大量的开发时间。我不是为了这个而写的。 :-)
  • @Ron Savage:Ron,我也有类似的代码,我在其中投入了大量时间来编写更通用的程序代码来解决重复出现的问题。但是...... SSRS 矩阵报告控件也是“通用的,适用于任何查询,而无需知道数据透视列的值”。既然你说你是为了报告而做的,我无法想象当 SSRS 可以开箱即用地完成这项工作时,为什么有人会想要使用程序代码。
  • 并非我们所有的应用程序都可以访问或需要使用报表服务器。我们也有使用矩阵的报告,并且我们发现对于旋转大型结果集,这个过程要快得多 - 因为只有汇总的结果被发送到报告服务器,而不是所有要旋转和汇总的原始数据控制。我也不确定你为什么称它为“程序性”,它基本上只是格式化数据并在内部构建 PIVOT 语句。
【解决方案3】:

我认为您可以使用嵌套的 while 循环和一些动态 SQL 来做到这一点。如果您无法保存最终表格或者您必须每月重新生成所有列,这将是一个缓慢的解决方案。但是,如果它只是添加剂,那么它可能还不错。不管怎样,我会这样做:

  1. 外循环选择最旧的年份。
  2. 内循环选择第一个月。
  3. Inner Inner loop - 将名为 - 的列添加到您的表中。
  4. 内部循环 - 使用动态 SQL 的新列的所有信息更新表
  5. 每个月遍历内部循环
  6. 每年迭代外循环。

【讨论】:

  • -1。很抱歉,RandomBen,但是当清晰且简单的基于集合的解决方案可用于取消旋转然后再次旋转时,循环根本不支持。循环的性能绝对是糟糕的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多