最近碰巧读到了关于sql server pivot 的两篇文章。详见:
1 Cross Tabs and Pivots, Part 1 – Converting Rows to Columns
2 Cross Tabs and Pivots, Part 2 - Dynamic Cross Tabs
这两篇文章对sql server里面行转列的两种技术cross tabs和pivot进行了分析和比较。然后着重介绍了动态cross tab的技术。其实这两种旋转在日常中用到的蛮多的,但是动态的旋转我以前考虑过,一直没有去实现,读了这两篇文章,收获颇丰。这两篇文章没有对动态pivot进行分析。其实动态pivot和动态cross tab原理上是一样的,只需要找到变化的部分,然后把这部分动态转换出来就可以了。本篇文章介绍动态pivot。
首先是填充样例数据表,这两篇文章中有介绍,我就借用下:
SELECT TOP 1000000 RowNum = IDENTITY(INT,1,1), SomeInt = ABS(CHECKSUM(NEWID()))%50000+1, SomeLetters2 = CHAR(ABS(CHECKSUM(NEWID()))%26+65) + CHAR(ABS(CHECKSUM(NEWID()))%26+65), SomeCSV = CAST('Part01,Part02,Part03,Part04,Part05,Part06,Part07,Part08,Part09,Part10' AS VARCHAR(80)), SomeMoney = CAST(ABS(CHECKSUM(NEWID()))%10000 /100.0 AS MONEY), SomeDate = CAST(RAND(CHECKSUM(NEWID()))*3653.0+36524.0 AS DATETIME), SomeHex12 = RIGHT(NEWID(),12) INTO dbo.JBMTest FROM Master.dbo.SysColumns t1 CROSS JOIN Master.dbo.SysColumns t2 --===== Add the Primary Key ALTER TABLE dbo.JBMTest ADD PRIMARY KEY CLUSTERED (RowNum)
select SomeLetters2, --dynamic part 1 coalesce([Dec 2005], 0) [Dec 2005], coalesce([Jan 2006], 0) [Jan 2006], coalesce([Feb 2006], 0) [Feb 2006], coalesce([Mar 2006], 0) [Mar 2006], coalesce([Apr 2006], 0) [Apr 2006], coalesce([ALL], 0) [ALL] -- start part from ( select case when grouping(SomeLetters2) = 1 then '1-Total' else SomeLetters2 end as SomeLetters2, case when GROUPING( DATEADD(mm, datediff(mm, 0, SomeDate), 0)) = 1 then'ALL' else stuff(convert(char(11), DATEADD(mm, datediff(mm, 0, SomeDate), 0), 100), 4, 3, '') end as dt, SUM(somemoney) sm from dbo.jbmtest where SomeDate >= '12-5-2005' and SomeDate <= '4-2-2006' and SomeLetters2 in ('AI', 'AJ', 'AC') group by SomeLetters2, DATEADD(mm, datediff(mm, 0, SomeDate), 0) with cube ) as d pivot( sum(sm) for dt in( --dynamic part 2 [Dec 2005],[Jan 2006],[Feb 2006],[Mar 2006],[Apr 2006],[ALL] ) ) as p order by SomeLetters2
declare @startDate datetime = '2005-12-5', @endDate datetime = '2006-3-2' set @endDate = DATEADD(MM, DATEDIFF(mm, 0, @enddate)+1, 0) select N, stuff(convert(char(11), DATEADD(mm, N-1, @startDate), 100), 4, 3, '') dtstr from dbo.Number where N <= DATEDIFF(mm, @startDate, @endDate)
declare @startDate datetime = '2005-12-5', @endDate datetime = '2006-1-2'; set @endDate = DATEADD(MM, DATEDIFF(mm, 0, @enddate)+1, 0) declare @part1 nvarchar(max) = 'select SomeLetters2,' + char(10) ; declare @temp1 nvarchar(4000) = 'coalesce([tmpRep], 0) [tmpRep],' + char(10); declare @temp2 nvarchar(4000) = '[tmpRep],'; declare @part2 nvarchar(max) = ''; select @part1 = @part1 + dtpart, @part2 = @part2 + dtpart2 from( select REPLACE(@temp1, 'tmpRep', d.dtstr) dtpart, REPLACE(@temp2, 'tmpRep', d.dtstr) dtpart2 from( select N, stuff(convert(char(11), DATEADD(mm, N-1, @startDate), 100), 4, 3, '') dtstr from dbo.Number where N <= DATEDIFF(mm, @startDate, @endDate) ) as d ) as d2 set @part1 = @part1 + REPLACE(@temp1, 'tmpRep', 'ALL') set @part2 = @part2 + REPLACE(@temp2, 'tmpRep', 'ALL') --remove the last chart ',' @part1 have char(10) and ',' in the right, remove 2 chars set @part1 = LEFT(@part1, len(@part1) - 2) set @part2 = LEFT(@part2, len(@part2) -1 ) print @part1 print @part2
select SomeLetters2,
coalesce([Dec 2005], 0) [Dec 2005],
coalesce([Jan 2006], 0) [Jan 2006],
coalesce([ALL], 0) [ALL]
declare @part3 nvarchar(max) = ' from ( select case when grouping(SomeLetters2) = 1 then ' + quotename('1-Total', '''') + ' else SomeLetters2 end as SomeLetters2, case when GROUPING( DATEADD(mm, datediff(mm, 0, SomeDate), 0)) = 1 then' + quotename('ALL', '''')+ ' else stuff(convert(char(11), DATEADD(mm, datediff(mm, 0, SomeDate), 0), 100), 4, 3, '''') end as dt, SUM(somemoney) sm from dbo.jbmtest where SomeDate >= ' + quotename(@startDate, '''') + ' and SomeDate < ' +quotename(@endDate, '''') + ' group by SomeLetters2, DATEADD(mm, datediff(mm, 0, SomeDate), 0) with cube ) as d pivot( sum(sm) for dt in(tmpRep) ) as p order by SomeLetters2 '; set @part3 = @part1 + REPLACE(@part3, 'tmpRep', @part2) print @part3 exec (@part3)
declare @startDate datetime = '2005-12-5', @endDate datetime = '2006-1-2'; set @endDate = DATEADD(MM, DATEDIFF(mm, 0, @enddate)+1, 0) declare @part1 nvarchar(max) = 'select SomeLetters2,' + char(10) ; declare @temp1 nvarchar(4000) = 'coalesce([tmpRep], 0) [tmpRep],' + char(10); declare @temp2 nvarchar(4000) = '[tmpRep],'; declare @part2 nvarchar(max) = ''; select @part1 = @part1 + dtpart, @part2 = @part2 + dtpart2 from( select REPLACE(@temp1, 'tmpRep', d.dtstr) dtpart, REPLACE(@temp2, 'tmpRep', d.dtstr) dtpart2 from( select N, stuff(convert(char(11), DATEADD(mm, N-1, @startDate), 100), 4, 3, '') dtstr from dbo.Number where N <= DATEDIFF(mm, @startDate, @endDate) ) as d ) as d2 set @part1 = @part1 + REPLACE(@temp1, 'tmpRep', 'ALL') set @part2 = @part2 + REPLACE(@temp2, 'tmpRep', 'ALL') --remove the last chart ',' @part1 have char(10) and ',' in the right, remove 2 chars set @part1 = LEFT(@part1, len(@part1) - 2) set @part2 = LEFT(@part2, len(@part2) -1 ) --print @part1 --print @part2 declare @part3 nvarchar(max) = ' from ( select case when grouping(SomeLetters2) = 1 then ' + quotename('1-Total', '''') + ' else SomeLetters2 end as SomeLetters2, case when GROUPING( DATEADD(mm, datediff(mm, 0, SomeDate), 0)) = 1 then' + quotename('ALL', '''')+ ' else stuff(convert(char(11), DATEADD(mm, datediff(mm, 0, SomeDate), 0), 100), 4, 3, '''') end as dt, SUM(somemoney) sm from dbo.jbmtest where SomeDate >= ' + quotename(@startDate, '''') + ' and SomeDate < ' +quotename(@endDate, '''') + ' group by SomeLetters2, DATEADD(mm, datediff(mm, 0, SomeDate), 0) with cube ) as d pivot( sum(sm) for dt in(tmpRep) ) as p order by SomeLetters2 '; set @part3 = @part1 + REPLACE(@part3, 'tmpRep', @part2) print @part3 exec (@part3)