旋转/数据透视表在分组列中为每个唯一值创建一列

时间:2017-11-10 15:16:26

标签: sql-server

标题可能有点迟钝,我很难简洁地解释这一点。

我的数据看起来像这样( SourceTable ):

Date        Type    High    Low
2017-01-10  cats    21.4    10.4
2017-01-10  dogs    20.4    8.4
2017-01-20  cats    40.1    21.2
2017-01-20  dogs    20.1    1.0

我有大约300种不同的"类型" (如在“类型”列中)。该类型是指向所有类型的表的外键。

类型表

Type
cats
dogs

并非所有类型都在每个日期都有代表。

我想让上表看起来像这样( FlippedTable ):

Date        Cats_High    Cats_Low    Dogs_High    Dogs_Low
2017-01-10  21.4         10.4        20.4         8.4
2017-01-20  40.1         21.2        20.1         1.0

我有一个工作解决方案,它在动态SQL中使用WHILE循环,通过不断更改它来添加新列并插入新数据来构建临时表。它有效,但执行需要花费大量时间。我觉得我的动态SQL方法效率低下,可能会用一两个轴来改进。

当然,我们可以争辩说,首先这样做是低效和愚蠢的,但不幸的是我有理由。

这是我的动态SQL方法:

DECLARE @type_counter INT = 0;
DECLARE @date_counter INT = 0;
DECLARE @all_types_total INT;
DECLARE @all_dates_total INT;

DECLARE @current_date DATE;
DECLARE @current_type NVARCHAR(80);
DECLARE @dynamic_sql NVARCHAR(max);

CREATE TABLE #FlippedTable([Date] DATE PRIMARY KEY);

SELECT @all_types_total=COUNT(*) FROM types;

WHILE @type_counter < @all_types_total
BEGIN
    SELECT @current_type=type FROM (
        SELECT
            type,
            ROW_NUMBER() OVER(ORDER BY type) as [RowNumber]
        FROM SourceTable)
    AS subquery WHERE [RowNumber] = @type_counter;

    SET @dynamic_sql = 'ALTER TABLE #FlippedTable ADD [' + @current_type + '_Low] NUMERIC(7,2) NULL, ['+@current_type+'_High] NUMERIC(7,2)'
    EXECUTE sp_executeSQL @dynamic_sql;

    SELECT @all_dates_total = COUNT(*) FROM SourceTable WHERE type = @current_type;
    WHILE @date_counter < @all_dates_total
    BEGIN
        SELECT @current_date = [Date] FROM (SELECT [Date], ROW_NUMBER() OVER(ORDER BY [Date]) AS [RowNumber] FROM SourceTable) AS subquery WHERE [RowNumber] = @date_counter;

        IF EXISTS(SELECT * FROM FlippedTable WHERE [Date] = @current_date)
            BEGIN
                SET @dynamic_sql = 'UPDATE FlippedTable SET [' + @current_type + '_Low] = tb2.[Low], ['+@current_type+'_High] = tb2.[High] FROM FlippedTable AS tb1 INNER JOIN SourceTable AS tb2 ON tb1.Date = tb2.Date WHERE tb2.type = ''' + @current_type + ''' AND tb2.Date = ''' + CAST(@current_date AS nvarchar(50)) + '''';
                EXECUTE sp_executeSQL @dynamic_sql;
            END
        ELSE
            BEGIN
                SET @dynamic_sql = 'INSERT INTO FlippedTable ([Date], ['+@current_type+'_Low], ['+@current_type+'_High] FROM SourceTable WHERE type = ''' + @current_type + ''' AND SourceTable.Date = ''' + CAST(@current_date AS nvarchar(50)) + '''';
            END
        SET @date_counter = @date_counter + 1;
    END;
    SET @date_counter = 0;
    SET @type_counter = @type_counter + 1;
END;

添加&#34;类型&#34;表非常罕见。我知道所有类型,所以如果必须的话,我可以对所有可能的列名进行硬编码,尽管它不理想。

我希望有一个PIVOT解决方案可以解决这个问题。在这种情况下,我还没有能够理解如何使用该工具。无论更好的解决方案是什么,我觉得它使用的是我目前不熟悉的工具。

1 个答案:

答案 0 :(得分:1)

应尽可能避免循环。您可以通过一些动态SQL

获得所需的结果

示例

Declare @SQL varchar(max) = '
Select *
 From (
        Select A.Date
              ,B.*
         From  YourTable A
         Cross Apply ( values (Type+''_High'',High)
                              ,(Type+''_Low'',Low)
                     ) B(Item,Value)

      ) A
 Pivot (max([Value]) For [Item] in (' + Stuff((Select Distinct ','+QuoteName(Type+'_High') 
                                                              +','+QuoteName(Type+'_Low') 
                                               From (Select Distinct Type From YourTable) A
                                               Order By 1 
                                               For XML Path('')),1,1,'')  + ') ) p'
Exec(@SQL);
--Print @SQL

<强>返回

Date        cats_High   cats_Low    dogs_High   dogs_Low
2017-01-10  21.40       10.40       20.40       8.40
2017-01-20  40.10       21.20       20.10       1.00