SQL PIVOT 多条记录到列

Posted

技术标签:

【中文标题】SQL PIVOT 多条记录到列【英文标题】:SQL PIVOT Multiple Records to columns 【发布时间】:2017-09-27 12:45:01 【问题描述】:

PIVOT 和其他高级 SQL 对我来说非常陌生。我的 SQL 经验仅限于基本的查询和连接。我一直在努力使用这些工具,并安排了培训来推进我的 SQL 教育。

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

我有一个包含数万条记录的表,我需要为其生成特殊报告。一个人的表可以有很多条记录,但是每条记录显然是唯一的记录,信息也唯一。

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

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

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

我希望在这里找到一些清晰/帮助,因为我完全被难住了。

【问题讨论】:

网站上有数百个关于sql server pivoting的问题,您是否尝试过查看类似的帖子? 为什么您想要的结果中有名为InterimSummativeOverall 的列?这些列名背后的逻辑是什么?它们当然不是来自数据。 【参考方案1】:

您将不得不使用动态 SQL 来获得所需的结果,因为在执行时未知数据集应基于的可能值。您还在多个列 [Observation][Observer] 上进行透视,这意味着您要么必须使用带有 GROUP BY 子句的 CASE 语句,要么将有问题的两列组合成一个透视值列。使这个查询更加复杂的是[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

【讨论】:

您帖子中的详细信息非常棒。我将查看您的示例,看看我是否可以模糊地理解并在我们的环境中实施它。如果这确实帮助我实现了我想要的结果,那么你就是一个巨大的帮助。乍一看...我觉得我应该在我们的应用程序中而不是在数据库中以编程方式执行此操作,但这纯粹是由于我缺乏 SQL 知识/理解。 @RoyDePhillip 很高兴我能提供帮助。如果您可以在应用程序中执行数据透视,那么我建议您这样做。动态 SQL 查询往往更难调试、维护,并且会使您面临潜在的 SQL 注入问题。【参考方案2】:

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

【讨论】:

以上是关于SQL PIVOT 多条记录到列的主要内容,如果未能解决你的问题,请参考以下文章

SQL Pivot 行到列标题

SQL - 多行到列 - 枢轴?交叉表?

使用 flex 实现四列到列列表的反馈 - 第 2 轮

Pivot_table MultiIndex 到列

Excel从列到列的映射

使用 PIVOT 函数的行到列 (Oracle)