2017-09-27 83 views
-1

PIVOT和其他高级SQL对我来说是非常新的。我对SQL的使用经验仅限于基本的查询和连接。我一直在试图围绕使用这些工具来打包头脑,并且安排了培训以推进我在SQL中的教育。SQL PIVOT多个记录到列

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

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

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

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

Illustration of input and desired output

如果PIVOT是不是为了这个,我理解正确的解决方案,但我想排除这种可能性,如果事实并非如此。我可以很容易地以编程方式执行我想要的任务,但是在数据库服务器而不是应用程序中执行此操作会显着减少报告生成时间。

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

+1

有数以百计的有关SQL Server的问题在网站上摆动,有你试着看类似的帖子? – Tanner

+0

为什么你想要的结果有名为'Interim',Summative'和'Overall'的列?这些列名背后的逻辑是什么?他们当然不是来自数据。 –

回答

1

您将不得不使用动态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 
+0

您的帖子中的详细信息非常棒。我会审查你的例子,看看我能否在我们的环境中模糊地理解和实施它。 如果这确实能帮助我达到我想要的效果,那么您一直是一个巨大的帮助。乍一看......我觉得我应该在应用程序中而不是在数据库中以编程方式执行此操作,但这纯粹是由于我缺乏SQL知识/理解。 –

+1

@RoyDePhillip很高兴我能帮到你。如果你可以在应用程序中执行数据透视,那么我建议你这样做。动态SQL查询往往难以调试,维护并使您面临潜在的SQL注入问题。 –