Proposal yang berfungsi, dengan beberapa sampel data, dapat ditemukan @ rextester: bigtable unpivot
Inti dari operasi:
1 - Gunakan syscolumns dan untuk xml untuk secara dinamis menghasilkan daftar kolom kami untuk operasi unpivot; semua nilai akan dikonversi ke varchar (maks), dengan NULL yang dikonversi ke string 'NULL' (ini membahas masalah dengan tidak dilewati melewatkan nilai NULL)
2 - Buat kueri dinamis untuk melepaskan data ke dalam tabel temp #columns
- Mengapa tabel temp vs CTE (via with clause)? berkaitan dengan masalah kinerja potensial untuk volume data yang besar dan CTE mandiri tanpa skema indeks / hashing yang dapat digunakan; tabel temp memungkinkan untuk pembuatan indeks yang akan meningkatkan kinerja pada self-join [lihat CTE lambat bergabung sendiri ]
- Data ditulis ke #kolom dalam urutan PK + ColName + UpdateDate, memungkinkan kami untuk menyimpan nilai PK / Colname di baris yang berdekatan; kolom identitas ( rid ) memungkinkan kita untuk bergabung sendiri dengan baris-baris ini secara berurutan melalui rid = rid + 1
3 - Lakukan join #temp table sendiri untuk menghasilkan output yang diinginkan
Memotong-n-menempel dari rextester ...
Buat beberapa sampel data dan tabel # kolom kami:
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)
);
Nyali dari solusi:
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
;
Dan hasilnya:
Catatan: permintaan maaf ... tidak bisa menemukan cara mudah untuk memotong-n-tempelkan output rextester ke dalam blok kode. Saya terbuka untuk saran.
Masalah / masalah potensial:
1 - konversi data ke varchar generik (maks) dapat menyebabkan hilangnya ketepatan data yang pada gilirannya dapat berarti kita kehilangan beberapa perubahan data; pertimbangkan datetime dan float pair berikut yang, ketika dikonversi / dilemparkan ke generik 'varchar (max)', kehilangan presisi mereka (yaitu, nilai yang dikonversi adalah sama):
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
Sementara presisi data dapat dipertahankan, itu akan memerlukan sedikit lebih banyak pengkodean (misalnya, casting berdasarkan tipe kolom sumber); untuk saat ini saya telah memilih untuk tetap menggunakan generik varchar (maks) sesuai rekomendasi OP (dan asumsi bahwa OP mengetahui data dengan cukup baik untuk mengetahui bahwa kami tidak akan mengalami masalah kehilangan presisi data).
2 - untuk set data yang sangat besar, kami berisiko membuang beberapa sumber daya server, apakah itu ruang tempdb dan / atau cache / memori; masalah utama berasal dari ledakan data yang terjadi selama unpivot (misalnya, kita beralih dari 1 baris dan 302 lembar data menjadi 300 baris dan 1200-1500 lembar data, termasuk 300 salinan kolom PK dan UpdateDate, 300 nama kolom)