Arbeitsvorschlag mit einigen Beispieldaten finden Sie unter @ rextester: bigtable unpivot
Der Kern der Operation:
1 - Verwenden Sie syscolumns und für xml , um unsere Spaltenlisten dynamisch für die Unpivot-Operation zu generieren. Alle Werte werden in varchar (max) konvertiert, wobei NULL-Werte in die Zeichenfolge 'NULL' konvertiert werden.
2 - Generieren Sie eine dynamische Abfrage, um die Pivotierung von Daten in der temporären Tabelle #columns aufzuheben
- Warum eine temporäre Tabelle gegen CTE (via with clause)? befasst sich mit potenziellen Leistungsproblemen bei einem großen Datenvolumen und einem CTE-Self-Join ohne verwendbares Index- / Hashing-Schema; In einer temporären Tabelle kann ein Index erstellt werden, der die Leistung beim Self-Join verbessern soll [siehe langsamer CTE-Self-Join ].
- Daten werden in der Reihenfolge PK + ColName + UpdateDate in #columns geschrieben, sodass wir PK / Colname-Werte in benachbarten Zeilen speichern können. Eine Identitätsspalte ( rid ) ermöglicht es uns, diese aufeinander folgenden Zeilen mit rid = rid + 1 selbst zu verbinden
3 - Führen Sie einen Self-Join der Tabelle #temp durch, um die gewünschte Ausgabe zu generieren
Schneiden-und-Einfügen von rextester ...
Erstellen Sie einige Beispieldaten und unsere Tabelle #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)
);
Der Mut der Lösung:
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
;
Und die Ergebnisse:
Hinweis: Entschuldigungen ... konnten keine einfache Möglichkeit zum Ausschneiden und Einfügen der Rextester-Ausgabe in einen Codeblock finden. Ich bin offen für Vorschläge.
Mögliche Probleme / Bedenken:
1 - Die Konvertierung von Daten in ein generisches varchar (max) kann zu einem Verlust der Datengenauigkeit führen, was wiederum bedeuten kann, dass wir einige Datenänderungen verpassen. Berücksichtigen Sie die folgenden datetime- und float-Paare, die beim Konvertieren / Umwandeln in das generische 'varchar (max)' ihre Genauigkeit verlieren (dh die konvertierten Werte sind dieselben):
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
Während die Datengenauigkeit aufrechterhalten werden könnte, wäre etwas mehr Codierung erforderlich (z. B. Casting basierend auf Quellenspaltendatentypen). Im Moment habe ich mich dafür entschieden, den generischen varchar (max) gemäß der Empfehlung des OP beizubehalten (und davon auszugehen, dass das OP die Daten gut genug kennt, um zu wissen, dass wir keine Probleme mit Datengenauigkeitsverlusten bekommen).
2 - bei sehr großen Datenmengen besteht die Gefahr, dass einige Serverressourcen aufgebraucht werden, unabhängig davon, ob es sich um temporären Speicherplatz und / oder Cache / Speicher handelt. Das Hauptproblem ergibt sich aus der Datenexplosion, die während eines Unpivots auftritt (z. B. gehen wir von 1 Zeile und 302 Datenelementen zu 300 Zeilen und 1200-1500 Datenelementen über, einschließlich 300 Kopien der PK- und UpdateDate-Spalten und 300 Spaltennamen).