【问题标题】:PIVOT with multiple columns具有多列的 PIVOT
【发布时间】:2014-12-12 22:25:57
【问题描述】:

我正在尝试以 SQL Server 2008 中发票表的两列为轴心。所以我有如下数据:

+--------------+--------+---------+------+
| Invoice Date | Item # | Dollars | Lbs. |
+--------------+--------+---------+------+
| 1/1/14       | A      |       1 |    1 |
| 1/2/14       | B      |       2 |    2 |
| 1/3/14       | A      |       3 |    3 |
| 1/4/14       | B      |       4 |    4 |
| 2/1/14       | A      |       5 |    5 |
| 2/1/14       | B      |       6 |    6 |
+--------------+--------+---------+------+

我想把它显示为

+--------+--------------+-----------------+--------------+-----------------+
| Item # | 1/31/14 Lbs. | 1/31/14 Dollars | 2/28/14 Lbs. | 2/28/14 Dollars |
+--------+--------------+-----------------+--------------+-----------------+
| A      |            4 |               4 |            5 |               5 |
| B      |            6 |               6 |            6 |               6 |
+--------+--------------+-----------------+--------------+-----------------+

请注意,列名是该月的最后一天以及美元或英镑。我可以在一栏(英镑或美元)上做到这一点,但我不能在两个栏上都做到。

这是我的英镑示例代码:

DECLARE
  @v_Columns VARCHAR(MAX),
  @v_Query VARCHAR(MAX)

  --pivot and delimit values

SELECT @v_Columns = COALESCE(@v_Columns,'[') + convert(varchar(8), InvoiceDate, 1) + ' Lbs.' + '],[' 
FROM 
( SELECT DISTINCT dbo.ufn_GetLastDayOfMonth(InvoiceDate) As InvoiceDate
  FROM Invoice 
  WHERE InvoiceDate BETWEEN @BEGIN_DATE AND @END_DATE
  ORDER BY InvoiceDate

--delete last two chars of string (the ending ',[')

SET @v_Columns = SUBSTRING(@v_Columns, 1, LEN(@v_Columns)-2)
PRINT @v_Columns

--construct sql statement

SET @v_Query = 

'WITH AllOrders (LastInvoiceDate, Item,  Pounds) AS 
(
   SELECT 
    CONVERT(varchar(8), dbo.ufn_GetLastDayOfMonth(Invoice.InvoiceDate), 1) + ''' + ' Lbs.' + ''' As LastInvoiceDate,
    Item, 
    Pounds 
   FROM INVOICE
   WHERE InvoiceDate BETWEEN @BEGIN_DATE AND  @END_DATE 
)
SELECT *
FROM AllOrders
PIVOT
(
  SUM(QuantityShipped)
  FOR LastInvoiceDate  IN (' + @v_Columns + ')
) AS pivotview'

提前谢谢大家!

【问题讨论】:

    标签: sql-server sql-server-2008 pivot unpivot


    【解决方案1】:

    为了获得结果,您将不得不 PIVOT 两次或将 DollarsLbs 列取消透视到单个列中,然后应用 PIVOT 一次。我的偏好是先取消旋转,然后再进行旋转,因为我发现它更容易。

    您应该先将查询编写为静态或硬编码版本以获取正确的逻辑,然后再将其转换为动态 SQL,而不是先动态工作。我的示例使用了您的最终日期201-01-31 等,因为您正在使用一个函数来创建这些日期并且应该能够根据需要应用它。

    由于您使用的是 SQL Server 2005+,因此您可以使用 CROSS APPLY 来取消旋转 DollarsLbs。代码将类似于以下内容:

    select 
      t.ItemNo,
      new_col = convert(varchar(10), t.[invoice date], 120) + '_'+ c.col,
      c.value
    from yourtable t
    cross apply
    (
      select 'Dollars', Dollars union all
      select 'Lbs', Lbs
    ) c (col, value);
    

    SQL Fiddle with Demo。这会将您的数据转换为以下格式:

    | ITEMNO |            NEW_COL | VALUE |
    |--------|--------------------|-------|
    |      A | 2014-01-31_Dollars |     1 |
    |      A |     2014-01-31_Lbs |     1 |
    |      B | 2014-01-31_Dollars |     2 |
    |      B |     2014-01-31_Lbs |     2 |
    |      A | 2014-01-31_Dollars |     3 |
    

    我已将您需要的最终列名连接到new_col。同样,您可以将日期格式化为您需要的任何格式,我只是使用了2014-01-31 并将DollarsLbs 添加到它的末尾。获得数据后,您将 PIVOT 值转换为最终所需的结果:

    select ItemNo,
      [2014-01-31_Lbs], [2014-01-31_Dollars],
      [2014-02-28_Lbs], [2014-02-28_Dollars]
    from
    (
      select 
        t.ItemNo,
        new_col = convert(varchar(10), t.[invoice date], 120) + '_'+ c.col,
        c.value
      from yourtable t
      cross apply
      (
        select 'Dollars', Dollars union all
        select 'Lbs', Lbs
      ) c (col, value)
    ) d
    pivot
    (
      sum(value)
      for new_col in ([2014-01-31_Lbs], [2014-01-31_Dollars],
                      [2014-02-28_Lbs], [2014-02-28_Dollars])
    ) p;
    

    SQL Fiddle with Demo。现在你已经得到了你想要的结果,所以只需将其转换为动态 SQL:

    DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX)
    
    select @cols = STUFF((SELECT ',' + QUOTENAME(convert(varchar(10), t.[invoice date], 120) + '_'+ c.col) 
                        from yourtable t
                        cross apply
                        (
                          select 'Lbs', 0 union all
                          select 'Dollars', 1
                        ) c (col, so)
                        group by [invoice date], col, so
                        order by [invoice date], so
                FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'')
    
    
    set @query = 'SELECT ItemNo,' + @cols + ' 
                from 
                (
                    select 
                      t.ItemNo,
                      new_col = convert(varchar(10), t.[invoice date], 120) + ''_''+ c.col,
                      c.value
                    from yourtable t
                    cross apply
                    (
                      select ''Dollars'', Dollars union all
                      select ''Lbs'', Lbs
                    ) c (col, value)
                ) d
                pivot 
                (
                    sum(value)
                    for new_col in (' + @cols + ')
                ) p '
    
    exec sp_executesql @query;
    

    SQL Fiddle with Demo。这给出了最终结果:

    | ITEMNO | 2014-01-31_LBS | 2014-01-31_DOLLARS | 2014-02-28_LBS | 2014-02-28_DOLLARS |
    |--------|----------------|--------------------|----------------|--------------------|
    |      A |              4 |                  4 |              5 |                  5 |
    |      B |              6 |                  6 |              6 |                  6 |
    

    【讨论】:

      【解决方案2】:

      这是您的示例表

        CREATE TABLE #TEMP([Invoice Date] DATE,[Item #] VARCHAR(10),[DollarS] NUMERIC(10,0),[Lbs.] NUMERIC(10,0))
          INSERT INTO #TEMP VALUES ('1/1/14', 'A',1,1)
          INSERT INTO #TEMP VALUES ('1/2/14', 'B',2,2)
          INSERT INTO #TEMP VALUES ('1/3/14', 'A',3,3)
          INSERT INTO #TEMP VALUES ('1/4/14', 'B',4,4)
          INSERT INTO #TEMP VALUES ('2/1/14', 'A',5,5)
          INSERT INTO #TEMP VALUES ('2/1/14', 'B',6,6)
      

      现在您需要应用UNION ALL(而不是UNPIVOT)并将列带到行并合并列,得到列的顺序为Date+LBS/DOLLARS

      SELECT DISTINCT DENSE_RANK() OVER(ORDER BY  CAST(LASTDAY AS DATE),UNIT DESC)RNO,*,
      CAST(DATEPART(MONTH,LASTDAY)AS VARCHAR) +'/'+ CAST(DATEPART(DAY,LASTDAY)AS VARCHAR) +'/' +RIGHT(CAST(YEAR(LASTDAY)AS VARCHAR),2)+' ' +UNIT  PIVOTCOL 
      INTO #NEWTABLE 
      FROM
      (
          SELECT [Item #],'DOLLARS' UNIT,
          DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))LASTDAY,
          SUM([Dollars]) OVER(PARTITION BY [Item #],DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))) VALUE
          FROM #TEMP
      
          UNION ALL
      
          SELECT [Item #], 'LBS.',
          DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))LASTDAY,
          SUM([Lbs.]) OVER(PARTITION BY [Item #],DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))) DOLLARSUM
          FROM #TEMP
      )TAB
      

      现在声明查询以动态获取列并设置NULL to Zero

      DECLARE @cols NVARCHAR (MAX)
      DECLARE @NullToZeroCols NVARCHAR (MAX)
      
      SELECT @cols = COALESCE (@cols + ',[' + PIVOTCOL + ']', 
                    '[' + PIVOTCOL + ']')
                     FROM    (SELECT DISTINCT RNO,PIVOTCOL FROM #NEWTABLE) PV  
                     ORDER BY RNO
      PRINT @COLS
      
      SET @NullToZeroCols = SUBSTRING((SELECT ',ISNULL(['+PIVOTCOL+'],0) AS ['+PIVOTCOL+']' 
      FROM(SELECT DISTINCT RNO,PIVOTCOL FROM #NEWTABLE GROUP BY RNO,PIVOTCOL)TAB  
      ORDER BY RNO  FOR XML PATH('')),2,8000)
      

      现在旋转查询

      DECLARE @query NVARCHAR(MAX)
      SET @query = 'SELECT [Item #],' + @NullToZeroCols + ' FROM 
                   (
                       SELECT [Item #],VALUE,PIVOTCOL FROM #NEWTABLE
                   ) x
                   PIVOT 
                   (
                       SUM(VALUE)
                       FOR PIVOTCOL IN (' + @cols + ')
                  ) p
                  ORDER BY [Item #];' 
      
      EXEC SP_EXECUTESQL @query
      

      结果

      【讨论】:

      • 是的。你说的对。这就是为什么我提到“我使用 UNION ALL 而不是 UNPIVOT”。 @bluefeet
      • 我已添加代码以将 NULL 值替换为枢轴中的零。如果您想要 NULL 值而不是零,请随时问我。我以简单的方式编写了代码。 @Mason Poehlman
      猜你喜欢
      • 2011-03-15
      • 2012-10-15
      • 2021-08-30
      • 2017-04-04
      • 1970-01-01
      • 1970-01-01
      • 2018-06-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多