【问题标题】:TSQL Pivoting Issue - looking for better approachSQL 透视问题 - 寻找更好的方法
【发布时间】:2015-11-24 11:52:15
【问题描述】:

这是一个与 T-SQL 相关的问题。我正在使用 SQL Server 2012。

我有一张这样的桌子:

我想要这样的输出:

解释:

对于每个员工,都会有一行。一名员工有一项或多项任务。批次 ID 指定了这一点。根据批次 ID,列名会发生变化(例如 Country 1、Country 2 等)。

目前的方法:

像下面这样取消透视源表:

select 
    EmpId, 'Country ' + cast(BatchId as varchar) as [ColumnName], 
    Country as [ColumnValue] 
from 
    SourceTable
UNION
select 
    EmpId, 'Pass ' + cast(BatchId as varchar) as [ColumnName], 
    Pass as [ColumnValue] 
from 
    SourceTable

将每列的值作为行给出。然后,可以对该结果进行旋转以获得所需的输出。

问题:

  • 有更好的方法吗?
  • 目前,我知道会有固定数量的批次,但是,对于未来,如果我想让旋转部分动态化,最好的方法是什么?
  • 使用 SSIS 或 SSRS 之类的工具,是否更容易动态地处理枢轴?

【问题讨论】:

  • 你做对了 UNPIVOT PIVOT 子句。如果您不知道确切的批次数,可以使用动态 sql。搜索动态旋转。这是标准方式。您获得最大批号并为该最大值构造数据透视语句。循环和游标是这里最糟糕的解决方案。
  • 添加了一个 +1 以抵消无缘无故投反对票的混蛋(如果他们有充分的理由,他们可能会说)。

标签: tsql sql-server-2012 pivot dynamic-sql


【解决方案1】:

有许多可能的解决方案可以实现您想要的(搜索Dynamic Pivot on multiple columns

SqlFiddleDemo

警告:我假设 Country 和 Pass 列不是 NULL

CREATE TABLE SourceTable(EmpId INT, BatchId INT,
             Country NVARCHAR(100) NOT NULL, Pass NVARCHAR(5) NOT NULL);

INSERT INTO SourceTable(EmpId, BatchId, Country, Pass)
VALUES 
(100, 1, 'UK', 'M'),        (200, 2, 'USA', 'U'),
(100, 2, 'Romania', 'M'),   (100, 3, 'India', 'MA'),
(100, 4, 'Hongkong', 'MA'), (300, 1, 'Belgium', 'U'),
(300, 2, 'Poland', 'U'),    (200, 1, 'Australia', 'M');


/* Get Number of Columns Groups Country1..Country<MaxCount> */
DECLARE @max_count INT
       ,@sql      NVARCHAR(MAX) = ''
       ,@columns  NVARCHAR(MAX) = ''
       ,@i        INT           = 0
       ,@i_s       NVARCHAR(10);

WITH cte AS
(
  SELECT EmpId
        ,[cnt] = COUNT(*)
  FROM SourceTable
  GROUP BY EmpId
)
SELECT @max_count = MAX(cnt)
FROM cte;

WHILE @i < @max_count
BEGIN
    SET @i += 1;
    SET @i_s = CAST(@i AS NVARCHAR(10));
    SET @columns += N',MAX(CASE WHEN [row_no] = ' + @i_s + ' THEN Country END) AS Country' + @i_s +
                     ',MAX(CASE WHEN [row_no] = ' + @i_s + ' THEN Pass END) AS Pass' + @i_s;   
END 

SELECT @sql =
   N';WITH cte AS (
   SELECT EmpId, Country, Pass, [row_no] = ROW_NUMBER() OVER (PARTITION BY EmpId ORDER BY BatchId)
   FROM SourceTable)
   SELECT EmpId ' +  @columns + N'
   FROM cte
   GROUP BY EmpId';

/* Debug */
/* SELECT @sql */

EXEC(@sql);

或者:

SQLFiddleDemo2

DECLARE @cols NVARCHAR(MAX),
        @sql  NVARCHAR(MAX) = '';

;WITH cte(col_name, rn) AS(
SELECT DISTINCT col_name = col_name + CAST(BatchId AS VARCHAR(10)),
       rn = ROW_NUMBER() OVER(PARTITION BY EmpId ORDER BY BatchId)
FROM SourceTable
CROSS APPLY (VALUES ('Country', Country), ('Pass', Pass)) AS c(col_name, val)
)
SELECT @cols = STUFF((SELECT ',' + QUOTENAME(col_name)
                      FROM cte
                      ORDER BY rn /* If column order is important for you */
                      FOR XML PATH(''), TYPE
                     ).value('.', 'NVARCHAR(MAX)') 
                        , 1, 1, '');

SET @sql =
 N';WITH cte AS 
   (
       SELECT EmpId, col_name = col_name + CAST(BatchId AS VARCHAR(10)), val
       FROM SourceTable
       CROSS APPLY (VALUES (''Country'', Country), (''Pass'', Pass)) AS c(col_name, val)
   )  
   SELECT *
   FROM cte
   PIVOT
   (
      MAX(val)
      FOR col_name IN (' + @cols + ')
    ) piv';

EXEC(@sql);

【讨论】:

    【解决方案2】:

    用 SQL 搞定。

    让 SSRS 使用 MATRIX 为您完成工作。它将为您 PIVOT,而无需创建动态 SQL 来处理需要知道所有列的可怕限制。

    对于您的数据,您可以将 EMP ID 作为 ROW 组,将 PASS 作为列分组。

    https://msdn.microsoft.com/en-us/library/dd207149.aspx

    【讨论】:

    • 感谢汉诺威拳头。我将为它寻求 SSRS 解决方案。 @lad2025,您的回答也很完美。但是,我只能选择一个可以接受的答案,所以很遗憾不能接受你的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-11
    • 2014-05-04
    • 1970-01-01
    • 1970-01-01
    • 2014-07-20
    相关资源
    最近更新 更多