可在rextester上找到带有一些示例数据的工作建议:bigtable unpivot
操作要点:
1-使用syscolumns和for xml为unpivot操作动态生成我们的列列表;所有值都将被转换为varchar(max),而将NULL转换为字符串'NULL'(此地址将解决无误跳过NULL值的问题)
2-生成动态查询以将数据取消透视化到#columns临时表中
- 为什么要使用临时表vs CTE(通过with子句)?涉及大量数据的潜在性能问题以及没有可用索引/哈希方案的CTE自联接;临时表允许创建索引,该索引应提高自连接的性能[请参见慢速CTE自连接 ]
- 数据以PK + ColName + UpdateDate的顺序写入#列,从而使我们可以在相邻行中存储PK / Colname值;标识列(rid)允许我们通过rid = rid + 1自联接这些连续的行
3-执行#temp表的自联接以生成所需的输出
从右旋糖膏中切出...
创建一些示例数据和我们的#columns表:
CREATE TABLE dbo.bigtable
(UpdateDate datetime not null
,PK varchar(12) not null
,col1 varchar(100) null
,col2 int null
,col3 varchar(20) null
,col4 datetime null
,col5 char(20) null
,PRIMARY KEY (PK)
);
CREATE TABLE dbo.bigtable_archive
(UpdateDate datetime not null
,PK varchar(12) not null
,col1 varchar(100) null
,col2 int null
,col3 varchar(20) null
,col4 datetime null
,col5 char(20) null
,PRIMARY KEY (PK, UpdateDate)
);
insert into dbo.bigtable values ('20170512', 'ABC', NULL, 6, 'C1', '20161223', 'closed')
insert into dbo.bigtable_archive values ('20170427', 'ABC', NULL, 6, 'C1', '20160820', 'open')
insert into dbo.bigtable_archive values ('20170315', 'ABC', NULL, 5, 'C1', '20160820', 'open')
insert into dbo.bigtable_archive values ('20170212', 'ABC', 'C1', 1, 'C1', '20160820', 'open')
insert into dbo.bigtable_archive values ('20170109', 'ABC', 'C1', 1, 'C1', '20160513', 'open')
insert into dbo.bigtable values ('20170526', 'XYZ', 'sue', 23, 'C1', '20161223', 're-open')
insert into dbo.bigtable_archive values ('20170401', 'XYZ', 'max', 12, 'C1', '20160825', 'cancel')
insert into dbo.bigtable_archive values ('20170307', 'XYZ', 'bob', 12, 'C1', '20160825', 'cancel')
insert into dbo.bigtable_archive values ('20170223', 'XYZ', 'bob', 12, 'C1', '20160820', 'open')
insert into dbo.bigtable_archive values ('20170214', 'XYZ', 'bob', 12, 'C1', '20160513', 'open')
;
create table #columns
(rid int identity(1,1)
,PK varchar(12) not null
,UpdateDate datetime not null
,ColName varchar(128) not null
,ColValue varchar(max) null
,PRIMARY KEY (rid, PK, UpdateDate, ColName)
);
解决方案的胆量:
declare @columns_max varchar(max),
@columns_raw varchar(max),
@cmd varchar(max)
select @columns_max = stuff((select ',isnull(convert(varchar(max),'+name+'),''NULL'') as '+name
from syscolumns
where id = object_id('dbo.bigtable')
and name not in ('PK','UpdateDate')
order by name
for xml path(''))
,1,1,''),
@columns_raw = stuff((select ','+name
from syscolumns
where id = object_id('dbo.bigtable')
and name not in ('PK','UpdateDate')
order by name
for xml path(''))
,1,1,'')
select @cmd = '
insert #columns (PK, UpdateDate, ColName, ColValue)
select PK,UpdateDate,ColName,ColValue
from
(select PK,UpdateDate,'+@columns_max+' from bigtable
union all
select PK,UpdateDate,'+@columns_max+' from bigtable_archive
) p
unpivot
(ColValue for ColName in ('+@columns_raw+')
) as unpvt
order by PK, ColName, UpdateDate'
--select @cmd
execute(@cmd)
--select * from #columns order by rid
;
select c2.PK, c2.UpdateDate, c2.ColName as ColumnName, c1.ColValue as 'Old Value', c2.ColValue as 'New Value'
from #columns c1,
#columns c2
where c2.rid = c1.rid + 1
and c2.PK = c1.PK
and c2.ColName = c1.ColName
and isnull(c2.ColValue,'xxx') != isnull(c1.ColValue,'xxx')
order by c2.UpdateDate, c2.PK, c2.ColName
;
结果:
注意:道歉...无法找出将nextester输出剪切-粘贴到代码块中的简单方法。我愿意提出建议。
潜在的问题/担忧:
1-将数据转换为通用varchar(max)可能会导致数据精度下降,这又意味着我们错过了一些数据更改;考虑以下日期时间和浮点对,当它们转换/广播为通用“ varchar(max)”时,它们会失去精度(即,转换后的值相同):
original value varchar(max)
------------------- -------------------
06/10/2017 10:27:15 Jun 10 2017 10:27AM
06/10/2017 10:27:18 Jun 10 2017 10:27AM
234.23844444 234.238
234.23855555 234.238
29333488.888 2.93335e+007
29333499.999 2.93335e+007
虽然可以保持数据精度,但需要更多的编码(例如,基于源列数据类型的转换);现在,我选择按照OP的建议坚持使用通用的varchar(max)(并假设OP非常了解数据,以至于我们不会遇到任何数据精度损失的问题)。
2-对于非常大的数据集,我们冒着耗尽一些服务器资源(无论是tempdb空间和/或缓存/内存)的风险;主要问题来自于取消透视期间发生的数据爆炸(例如,我们从1行和302条数据变为300行和1200-1500条数据,包括PK和UpdateDate列的300个副本,300个列的名称)