将列返回为基于月数的行

时间:2012-10-23 02:26:44

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

这似乎是一件很简单的事情,但我不确定我是否正确地考虑它以获得理想的结果。我正在使用一个支点,但我认为我需要一些与之配对的东西。

我有一张发票表,其中包含每个客户的月度发票。最多,客户每年将有12张发票,每月1张。

+----------+-------+-------+--------------+--------------+--------------+
| ClientID | Month | Year  | ColumnValue1 | ColumnValue2 | ColumnValue3 |
+----------+-------+-------+--------------+--------------+--------------+
|        1 |     1 |  2012 |           20 |           30 |           50 |
|        1 |     2 |  2012 |           25 |           35 |           40 |
|        2 |     1 |  2012 |           28 |           38 |           48 |
+----------+-------+-------+--------------+--------------+--------------+

现在,我想根据每个客户端创建一个如下所示的列表。每个月都会有一个专栏。所以客户端1看起来像:

+--------------+----+----+---+---+---+---+---+---+---+----+----+----+-------+
|  ColumnName  | 1  | 2  | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Total |
+--------------+----+----+---+---+---+---+---+---+---+----+----+----+-------+
| ColumnValue1 | 20 | 25 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  0 |  0 |  0 |   45  |
| ColumnValue2 | 30 | 35 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  0 |  0 |  0 |   65  |
| ColumnValue3 | 50 | 40 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |  0 |  0 |  0 |   90  |
+--------------+----+----+---+---+---+---+---+---+---+----+----+----+-------+

3 个答案:

答案 0 :(得分:4)

这可以使用SQL Server中的UNPIVOTPIVOT函数来完成。如果您有已知数量的列,则可以使用静态版本:

select clientid,
  col, year,
  isnull([1], 0) [1], 
  isnull([2], 0) [2], 
  isnull([3], 0) [3], 
  isnull([4], 0) [4], 
  isnull([5], 0) [5], 
  isnull([6], 0) [6], 
  isnull([7], 0) [7], 
  isnull([8], 0) [8], 
  isnull([9], 0) [9], 
  isnull([10], 0) [10], 
  isnull([11], 0) [11], 
  isnull([12], 0) [12],
  (isnull([1], 0) + isnull([2], 0) + isnull([3], 0) 
   + isnull([4], 0) + isnull([5], 0) + isnull([6], 0) 
   + isnull([7], 0) + isnull([8], 0) + isnull([9], 0) 
   + isnull([10], 0) + isnull([11], 0) + isnull([12], 0) ) Total
from
(
  select clientid, col, month, year, value
  from yourtable
  unpivot
  (
    value for col in (ColumnValue1, ColumnValue2, ColumnValue3)
  ) u
) x
pivot
(
  sum(value)
  for month in ([1], [2], [3], [4], [5], [6], [7], 
                [8], [9], [10], [11], [12])
) p

请参阅SQL Fiddle with Demo

但是使用动态sql执行此操作可能要容易得多,然后编写的代码就会减少,这将根据数据样本中的内容调整月数:

DECLARE @colsUnpivot AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX),
    @colsPivot as  NVARCHAR(MAX),
    @colsTotal as  NVARCHAR(MAX),
    @colsNull as  NVARCHAR(MAX)

select @colsUnpivot = stuff((select ','+ quotename(C.name)
         from sys.columns as C
         where C.object_id = object_id('yourtable') and
               C.name like 'ColumnValue%'
         for xml path('')), 1, 1, '')

select @colsPivot = STUFF((SELECT distinct ', '  + quotename(Month)
                    from yourtable t
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

select @colsNull = STUFF((SELECT distinct ', IsNull(' 
                           + quotename(Month) + ', 0) as '+quotename(Month)
                    from yourtable t
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

select @colsTotal = STUFF((SELECT distinct '+ IsNull(' 
                           + quotename(Month) + ', 0)'
                    from yourtable t
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query 
  = 'select clientid,
          year,
          '+@colsNull+', '+@colsTotal+' as Total
      from
      (
        select clientid, col, month, year, value
        from yourtable
        unpivot
        (
          value for col in ('+@colsUnpivot+')
        ) u
      ) x
      pivot
      (
        sum(value)
        for month in('+ @colspivot +')
      ) p'

exec(@query)

请参阅SQL Fiddle with Demo

两者都会产生相同的结果,区别在于第二个会根据表格中的数据进行调整:

| CLIENTID | YEAR |  1 |  2 | TOTAL |
-------------------------------------
|        1 | 2012 | 20 | 25 |    45 |
|        1 | 2012 | 30 | 35 |    65 |
|        1 | 2012 | 50 | 40 |    90 |
|        2 | 2012 | 28 |  0 |    28 |
|        2 | 2012 | 38 |  0 |    38 |
|        2 | 2012 | 48 |  0 |    48 |

答案 1 :(得分:3)

试试这个

Declare @t Table(ClientId Int,[Month] Int,[Year] Int,ColumnValue1 Int,ColumnValue2 Int, ColumnValue3 Int)
Insert Into @t Values(1,1,2012,20,30,50),(1,2,3012,25,35,40),(2,1,2012,28,38,48)

;With Cte As
(
    Select ClientId,[Month],ColumnName,ColumnNameValues
    From @t 
    UnPivot(ColumnNameValues For ColumnName In (ColumnValue1,ColumnValue2,ColumnValue3)) As unpvt
)

Select ClientId,
        ColumnName 
        ,[1] = Coalesce([1],0)
        ,[2] = Coalesce([2],0)
        ,[3] = Coalesce([3],0)
        ,[4] = Coalesce([4],0)
        ,[5] = Coalesce([5],0)
        ,[6] = Coalesce([6],0)
        ,[7] = Coalesce([7],0)
        ,[8] = Coalesce([8],0)
        ,[9] = Coalesce([9],0)
        ,[10]= Coalesce([10],0)
        ,[11]= Coalesce([11],0)
        ,[12] = Coalesce([12],0)
        ,Total = Coalesce([1],0) + Coalesce([2],0) + Coalesce([3],0) + Coalesce([4],0) + 
                 Coalesce([5],0) + Coalesce([6],0) + Coalesce([7],0) + Coalesce([8],0) + 
                 Coalesce([9],0) + Coalesce([10],0) + Coalesce([11],0) + Coalesce([12],0) 
From  Cte 
PIVOT 
(   
    MAX(ColumnNameValues) For [Month] In ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12] )   
) As pvt 
--Where ClientId =1 -- uncomment for specific client report
Order By 1

<强>结果

enter image description here

答案 2 :(得分:2)

Create table sequence (seqid bigint)
go

--Create a table which has sequence from 1 to 12 for monthId
Insert into sequence
Select Top 12 ROW_NUMBER() over(order by name)
from sys.objects
go


USE tempdb 
GO

CREATE TABLE TestReport
(
        ClientId int 
        ,MonthId int 
        ,YearId int 
        ,val1 int 
        ,val2 int 
        ,val3 int 
)
go


insert into TestReport
Select 1, 1,2012, 20,30,50
union 
Select 1,2,2012,25, 35, 40
union 
Select 2, 1, 2012, 28,38,48

Select *
from testReport


--Cross join with the Sequence table to get rows for each month

Select clientId
        , seqid as monthId
        , YearId 
        , case when MonthId = seqid then val1 else 0 end val1
        , case when MonthId = seqid then val2 else 0 end val2
        , case when MonthId = seqid then val3 else 0 end val3
into #Temp
from sequence seq
cross join  testReport rpt
where seq.seqid <=12


    --Select *      from #Temp

SELECT 'ColumnValue1' AS [columnName],   [1], [2], [3], [4],[5],[6],[7],[8],[9],[10],[11],[12]
,[1]+  [2]+  [3]+  [4]+ [5]+ [6]+ [7]+ [8]+ [9]+ [10]+ [11]+ [12] as Total
 FROM
(SELECT monthId, val1 
    FROM #Temp
    where ClientId =1 

    ) AS SourceTable
PIVOT
(
max(val1) FOR MonthId IN ( [1], [2], [3], [4],[5],[6],[7],[8],[9],[10],[11],[12])
) 
AS PivotTable


go

Drop table #Temp
Drop table sequence 
drop table TestReport
go