SQL PIVOT多列记录到列

时间:2017-09-27 12:45:02

标签: sql sql-server database pivot

PIVOT和其他高级SQL对我来说都是新手。我对SQL的体验仅限于基本查询和连接。我一直在尝试使用这些工具,并安排培训以提高我的SQL教育。

与此同时,我希望得到SO社区的一些指导。

我有一个表,其中包含生成特殊报告所需的数万条记录。该表可以包含单个人的许多记录,但每条记录显然都是具有唯一信息的唯一记录。

报告生成工具将在特定时间段内请求特定人员的数据。这通常会列出1-7条记录。我相信我需要PIVOT将每条记录显示为每个人的列。

我附上了一张图片,试图说明起点和所需的输出。我的同事和我花时间试图这样做,他使用了PIVOT,但不是几年,因为他最近不需要它。

Illustration of input and desired output

如果PIVOT不是我理解的正确解决方案,但如果不是,我想排除它。我认为,我可以很容易地以编程方式完成我想要的工作,但是在数据库服务器上而不是在应用程序中执行此工作将大大减少报告生成时间。

我希望在这里找到一些明确/帮助,因为我完全被难倒了。

2 个答案:

答案 0 :(得分:2)

对于这种输出,您应该将PIVOT与动态sql一起使用。在这篇文章Insert column alias from table to query results中有一个很好的PIVOT-UNPIVOT示例和在PIVOT中使用动态sql。您可以查找此帖子,了解有关PIVOT和UNPIVOT Using PIVOT and UNPIVOT

的更多信息

答案 1 :(得分:2)

您必须使用动态SQL来实现所需的结果,因为在执行时,数据集应该在其上旋转的可能值是未知的。您还可以使用多个列[Observation]& [Observer]这意味着您必须使用带有CASE子句的GROUP BY语句,或者将有问题的两列合并到一个数据透视值列中。使这个查询更加复杂的是[Type]似乎会影响结果的显示方式。

我已经创建了一个示例,说明如何在下面获得所需的结果。我做了以下假设:

  • 类型字段的值定义如下:1 =观察,2 = 临时,3 =总结,6 =总体

  • 根据您的样本输出,在旋转结果中只需要出现一次临时,总结和总体观察。

  • 根据您的样本输出,不会显示观察者的临时,总结和整体观察

示例:

-- Create some sample data
DECLARE @Observations TABLE
(
     [Name]         NVARCHAR(50) NOT NULL
    ,[Building]     NVARCHAR(50) NOT NULL
    ,[Observation]  DATETIME
    ,[Observer]     NVARCHAR(50) NOT NULL
    ,[Type]         INT
);

INSERT INTO @Observations
SELECT 'Doe, John', 'HQ', '01/01/2017', 'Doe, Jack', 1 UNION ALL
SELECT 'Doe, John', 'HQ', '02/01/2017', 'Doe, Jack', 1 UNION ALL
SELECT 'Doe, John', 'HQ', '03/01/2017', 'Doe, Jack', 2 UNION ALL
SELECT 'Doe, John', 'HQ', '04/01/2017', 'Doe, Jack', 1 UNION ALL
SELECT 'Doe, John', 'HQ', '05/01/2017', 'Doe, Jack', 1 UNION ALL
SELECT 'Doe, John', 'HQ', '06/01/2017', 'Doe, Jack', 3 UNION ALL
SELECT 'Doe, John', 'HQ', '07/01/2017', 'Doe, Jack', 6;

DECLARE @columns NVARCHAR(MAX) = N'';
DECLARE @selectClause NVARCHAR(MAX) = N'';
DECLARE @sql NVARCHAR(MAX);

CREATE TABLE #IntermResults
(
     [Name]         NVARCHAR(50) NOT NULL       
    ,[Building]     NVARCHAR(50) NOT NULL   
    ,[PivotValue]   NVARCHAR(50) NOT NULL   
    ,[PivotID]      NVARCHAR(50) NOT NULL
    ,[Observation]  DATETIME NOT NULL
    ,[OrderRank]    INT NOT NULL
)

-- Prepare the source data to make pivoting into desired format easier and store results into an interim temporary table.
;WITH ObservationsCTE
AS
(
    SELECT   [Name]         
            ,[Building]     
            ,[Observation]  
            ,[Observer]     
            ,[Type]
            ,(  
                CASE [Type]
                    WHEN 1 THEN 'Observation'
                    WHEN 2 THEN 'Interim'
                    WHEN 3 THEN 'Summative'
                    WHEN 6 THEN 'Overall'
                    ELSE 'UNKNOW'
                END
             ) AS [ObservationType] -- Give descriptive values to Type column’s values. This will be used to help generate pivot table column names.
    FROM    @Observations   
), ObservationsWithPivotIDs
AS
(
    SELECT   TOP 1000
             [Name]         
            ,[Building]     
            ,CAST([Observation] AS NVARCHAR(50)) AS [PivotValue] -- Pivot value effectively combines the Observation & Observer column to help simplify the pivot query.
            ,'Observation' + CAST(ROW_NUMBER() OVER (PARTITION BY [Name] ORDER BY [Observation]) AS NVARCHAR(10)) AS [PivotID] -- Create a pivot id that will be used to generate pivoted columns.
            ,[Observation]
            ,1 AS [OrderRank] -- use this field to help order records.
    FROM    ObservationsCTE
    WHERE   [Type] = 1
    UNION ALL
    SELECT   TOP 1000
             [Name]         
            ,[Building]     
            ,[Observer] AS [PivotValue] -- Pivot value effectively combines the Observation & Observer column to help simplify the pivot query.
            ,'Observer' + CAST(ROW_NUMBER() OVER (PARTITION BY [Name] ORDER BY [Observation]) AS NVARCHAR(10)) AS [PivotID] -- Create a pivot id that will be used to generate pivoted columns.
            ,[Observation]
            ,2 AS [OrderRank] -- use this field to help order records.
    FROM    ObservationsCTE
    WHERE   [Type] = 1
    UNION ALL
    SELECT   TOP 1000
             [Name]         
            ,[Building]     
            ,CAST([Observation] AS NVARCHAR(50)) AS [PivotValue] -- Pivot value effectively combines the Observation & Observer column to help simplify the pivot query.
            ,[ObservationType]  AS [PivotID]
            ,[Observation]
            ,1 AS [OrderRank] -- use this field to help order records.
    FROM    ObservationsCTE
    WHERE   [Type] <> 1
)
INSERT INTO #IntermResults  -- Insert the results into an intermediate table.
(
     [Name]         
    ,[Building]     
    ,[PivotValue]   
    ,[PivotID]      
    ,[Observation]  
    ,[OrderRank]    
)
SELECT   [Name]         
        ,[Building]     
        ,[PivotValue]   
        ,[PivotID]      
        ,[Observation]  
        ,[OrderRank]    
FROM    ObservationsWithPivotIDs

-- Determine what columns will be created for the pivot
SELECT   @columns += N', ' + QUOTENAME([PivotID])
        ,@selectClause += N', ' + (CASE [OrderRank]
                                        WHEN  1 THEN 'CAST(' + QUOTENAME([PivotID]) + ' AS DATETIME) AS ' + QUOTENAME([PivotID])
                                        ELSE [PivotID] 
                                    END)
FROM     #IntermResults
ORDER BY [Observation], [OrderRank]

-- Create dynamic query to create the pivot
SET @sql = N'
SELECT [Name], [Building], ' + STUFF(@selectClause, 1, 2, '') + '
FROM (
        SELECT  [Name]
                ,[Building]
                ,[PivotValue]
                ,[PivotID]
        FROM    #IntermResults
    ) i
PIVOT
(
  MAX([PivotValue]) FOR [PivotID] IN ('
  + STUFF(@columns, 1, 1, '')
  + ')
) AS p;';

PRINT @sql;
-- Execute dynamic pivot query
EXEC sp_executesql @sql;
-- Drop intermediate results
DROP TABLE #IntermResults