Kueri yang merinci perbedaan antara baris untuk sejumlah besar data

15

Saya memiliki sejumlah tabel besar, masing-masing dengan> 300 kolom. Aplikasi yang saya gunakan membuat "arsip" dari baris yang diubah dengan membuat salinan baris saat ini di tabel sekunder.

Pertimbangkan contoh sepele:

CREATE TABLE dbo.bigtable
(
  UpdateDate datetime,
  PK varchar(12) PRIMARY KEY,
  col1 varchar(100),
  col2 int,
  col3 varchar(20),
  .
  .
  .
  colN datetime
);

Tabel arsip:

CREATE TABLE dbo.bigtable_archive
(
  UpdateDate datetime,
  PK varchar(12) NOT NULL,
  col1 varchar(100),
  col2 int,
  col3 varchar(20),
  .
  .
  .
  colN datetime
);

Sebelum pembaruan apa pun dijalankan dbo.bigtable, salinan baris dibuat dbo.bigtable_archive, lalu dbo.bigtable.UpdateDatediperbarui dengan tanggal saat ini.

Oleh karena itu UNION, satukan dua tabel bersama & dikelompokkan dengan PKmembuat garis waktu perubahan, saat dipesan oleh UpdateDate.

Saya ingin membuat laporan yang merinci perbedaan antara baris, dipesan oleh UpdateDate, dikelompokkan berdasarkan PK, dalam format berikut:

PK,   UpdateDate,  ColumnName,  Old Value,   New Value

Old Valuedan New Valuebisa kolom yang relevan dilemparkan ke VARCHAR(MAX)(tidak ada TEXTatauBYTE kolom yang terlibat), karena saya tidak perlu melakukan post-processing nilai-nilai itu sendiri.

Saat ini saya tidak dapat memikirkan cara yang waras untuk melakukan hal ini untuk sejumlah besar kolom, tanpa harus membuat kueri secara terprogram - saya mungkin harus melakukan ini.

Terbuka untuk banyak ide, jadi saya akan menambahkan hadiah untuk pertanyaan setelah 2 hari.

Philᵀᴹ
sumber

Jawaban:

15

Ini tidak akan terlihat cantik, terutama mengingat lebih dari 300 kolom dan tidak tersedianya LAG, juga tidak akan berkinerja sangat baik, tetapi hanya sebagai sesuatu untuk memulai, saya akan mencoba pendekatan berikut:

  • UNION dua tabel.
  • Untuk setiap PK dalam set gabungan, dapatkan "inkarnasi" sebelumnya dari tabel arsip (implementasi di bawah ini menggunakan OUTER APPLY+ TOP (1)sebagai orang miskinLAG ).
  • varchar(max)Keluarkan setiap kolom data ke dan batalkan pengaruhnya secara berpasangan, yaitu nilai saat ini dan sebelumnya (CROSS APPLY (VALUES ...) berfungsi dengan baik untuk operasi ini).
  • Terakhir, filter hasil berdasarkan apakah nilai dalam setiap pasangan berbeda satu sama lain.

Transact-SQL di atas seperti yang saya lihat:

WITH
  Combined AS
  (
    SELECT * FROM dbo.bigtable
    UNION ALL
    SELECT * FROM dbo.bigtable_archive
  ) AS derived,
  OldAndNew AS
  (
    SELECT
      this.*,
      OldCol1 = last.Col1,
      OldCol2 = last.Col2,
      ...
    FROM
      Combined AS this
      OUTER APPLY
      (
        SELECT TOP (1)
          *
        FROM
          dbo.bigtable_archive
        WHERE
          PK = this.PK
          AND UpdateDate < this.UpdateDate
        ORDER BY
          UpdateDate DESC
      ) AS last
  )
SELECT
  t.PK,
  t.UpdateDate,
  x.ColumnName,
  x.OldValue,
  x.NewValue
FROM
  OldAndNew AS t
  CROSS APPLY
  (
    VALUES
    ('Col1', CAST(t.OldCol1 AS varchar(max), CAST(t.Col1 AS varchar(max))),
    ('Col2', CAST(t.OldCol2 AS varchar(max), CAST(t.Col2 AS varchar(max))),
    ...
  ) AS x (ColumnName, OldValue, NewValue)
WHERE
  NOT EXISTS (SELECT x.OldValue INTERSECT x.NewValue)
ORDER BY
  t.PK,
  t.UpdateDate,
  x.ColumnName
;
Andriy M
sumber
13

Jika Anda membatalkan pengubahan data ke tabel temp

create table #T
(
  PK varchar(12) not null,
  UpdateDate datetime not null,
  ColumnName nvarchar(128) not null,
  Value varchar(max),
  Version int not null
);

Anda bisa mencocokkan baris untuk menemukan nilai baru dan lama dengan bergabung sendiri PK, ColumnNamedan Version = Version + 1.

Bagian yang tidak begitu cantik, tentu saja, melakukan unpivot dari 300 kolom Anda ke tabel temp dari dua tabel dasar.

XML ke penyelamatan untuk membuat hal-hal yang kurang canggung.

Dimungkinkan untuk melakukan unpivot data dengan XML tanpa harus tahu kolom apa yang sebenarnya ada dalam tabel yang akan diproteksi. Nama kolom harus valid sebagai nama elemen dalam XML atau akan gagal.

Idenya adalah untuk membuat satu XML untuk setiap baris yang memiliki semua nilai untuk baris itu.

select bt.PK,
       bt.UpdateDate,
       (select bt.* for xml path(''), elements xsinil, type) as X
from dbo.bigtable as bt;
<UpdateDate xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2001-01-03T00:00:00</UpdateDate>
<PK xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">PK1</PK>
<col1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">c1_1_3</col1>
<col2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3</col2>
<col3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
<colN xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2001-01-03T00:00:00</colN>

elements xsinilada untuk membuat elemen untuk kolom dengan NULL.

XML kemudian dapat dihancurkan menggunakan nodes('*') untuk mendapatkan satu baris untuk setiap kolom dan gunakan local-name(.)untuk mendapatkan nama elemen dan text()untuk mendapatkan nilai.

  select C1.PK,
         C1.UpdateDate,
         T.X.value('local-name(.)', 'nvarchar(128)') as ColumnName,
         T.X.value('text()[1]', 'varchar(max)') as Value
  from C1
    cross apply C1.X.nodes('row/*') as T(X)

Solusi lengkap di bawah ini. Catatan yang Versionterbalik. 0 = Versi terakhir.

create table #X
(
  PK varchar(12) not null,
  UpdateDate datetime not null,
  Version int not null,
  RowData xml not null
);

create table #T
(
  PK varchar(12) not null,
  UpdateDate datetime not null,
  ColumnName nvarchar(128) not null,
  Value varchar(max),
  Version int not null
);


insert into #X(PK, UpdateDate, Version, RowData)
select bt.PK,
       bt.UpdateDate,
       0,
       (select bt.* for xml path(''), elements xsinil, type)
from dbo.bigtable as bt
union all
select bt.PK,
       bt.UpdateDate,
       row_number() over(partition by bt.PK order by bt.UpdateDate desc),
       (select bt.* for xml path(''), elements xsinil, type)
from dbo.bigtable_archive as bt;

with C as 
(
  select X.PK,
         X.UpdateDate,
         X.Version,
         T.C.value('local-name(.)', 'nvarchar(128)') as ColumnName,
         T.C.value('text()[1]', 'varchar(max)') as Value
  from #X as X
    cross apply X.RowData.nodes('*') as T(C)
)
insert into #T (PK, UpdateDate, ColumnName, Value, Version)
select C.PK,
       C.UpdateDate,
       C.ColumnName,
       C.Value,
       C.Version
from C 
where C.ColumnName not in (N'PK', N'UpdateDate');

/*
option (querytraceon 8649);

The above query might need some trick to go parallel.
For the testdata I had on my machine exection time is 16 seconds vs 2 seconds
https://sqlkiwi.blogspot.com/2011/12/forcing-a-parallel-query-execution-plan.html
http://dataeducation.com/next-level-parallel-plan-forcing-an-alternative-to-8649/

*/

select New.PK,
       New.UpdateDate,
       New.ColumnName,
       Old.Value as OldValue,
       New.Value as NewValue
from #T as New
  left outer join #T as Old
    on Old.PK = New.PK and
       Old.ColumnName = New.ColumnName and
       Old.Version = New.Version + 1;
Mikael Eriksson
sumber
6

Saya sarankan Anda pendekatan lain.

Meskipun Anda tidak dapat mengubah aplikasi saat ini, mungkin Anda bisa mengubah perilaku basis data.

Jika memungkinkan, saya akan menambahkan dua PEMICU ke tabel saat ini.

Satu BUKAN INSERT di dbo.bigtable_archive yang menambahkan catatan baru hanya jika saat ini tidak ada.

CREATE TRIGGER dbo.IoI_BTA
ON dbo.bigtable_archive
INSTEAD OF INSERT
AS
BEGIN
    IF NOT EXISTs(SELECT 1 
                  FROM dbo.bigtable_archive bta
                  INNER JOIN inserted i
                  ON  bta.PK = i.PK
                  AND bta.UpdateDate = i.UpdateDate)
    BEGIN
        INSERT INTO dbo.bigtable_archive
        SELECT * FROM inserted;
    END
END

Dan pemicu SETELAH INSERT pada bigtable yang melakukan pekerjaan yang persis sama, tetapi menggunakan data bigtable.

CREATE TRIGGER dbo.IoI_BT
ON dbo.bigtable
AFTER INSERT
AS
BEGIN
    IF NOT EXISTS(SELECT 1 
                  FROM dbo.bigtable_archive bta
                  INNER JOIN inserted i
                  ON  bta.PK = i.PK
                  AND bta.UpdateDate = i.UpdateDate)
    BEGIN
        INSERT INTO dbo.bigtable_archive
        SELECT * FROM inserted;
    END
END

Oke, saya telah membuat contoh kecil di sini dengan nilai awal ini:

SELECT * FROM bigtable;
SELECT * FROM bigtable_archive;
Perbarui Tanggal | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
02/01/2017 00:00:00 | ABC | C3 | 1 | C1  

Perbarui Tanggal | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
01/01/2017 00:00:00 | ABC | C1 | 1 | C1  

Sekarang Anda harus memasukkan ke dalam bigtable_archive semua catatan yang tertunda dari bigtable.

INSERT INTO bigtable_archive
SELECT *
FROM   bigtable
WHERE  UpdateDate >= '20170102';
SELECT * FROM bigtable_archive;
GO
Perbarui Tanggal | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
01/01/2017 00:00:00 | ABC | C1 | 1 | C1  
02/01/2017 00:00:00 | ABC | C3 | 1 | C1  

Sekarang, saat berikutnya aplikasi mencoba menyisipkan catatan pada tabel bigtable_archive, pemicu akan mendeteksi jika ada, dan memasukkannya akan dihindari.

INSERT INTO dbo.bigtable_archive VALUES('20170102', 'ABC', 'C3', 1, 'C1');
GO
SELECT * FROM bigtable_archive;
GO
Perbarui Tanggal | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
01/01/2017 00:00:00 | ABC | C1 | 1 | C1  
02/01/2017 00:00:00 | ABC | C3 | 1 | C1  

Jelas sekarang Anda bisa mendapatkan timeline perubahan hanya dengan menanyakan tabel arsip. Dan aplikasi tidak akan pernah menyadari bahwa pemicu diam-diam melakukan pekerjaan di bawah selimut.

Aku di sini

McNets
sumber
4

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:

masukkan deskripsi gambar di sini

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)

markp
sumber
1

Pendekatan ini menggunakan kueri dinamis untuk menghasilkan sql untuk mendapatkan perubahan. SP mengambil nama tabel & skema dan memberikan output yang Anda inginkan.

Asumsinya adalah bahwa kolom PK dan UpdateDate ada di semua tabel. Dan semua tabel arsip memiliki format originalTableName + "_archive" ..

NB: Saya belum memeriksanya untuk kinerja.

NB: karena ini menggunakan sql dinamis, saya harus menambahkan peringatan tentang keamanan / injeksi sql. Batasi akses ke SP & tambahkan validasi lain untuk mencegah injeksi sql.

    CREATE proc getTableChanges
    @schemaname  varchar(255),
    @tableName varchar(255)
    as

    declare @strg nvarchar(max), @colNameStrg nvarchar(max)='', @oldValueString nvarchar(max)='', @newValueString nvarchar(max)=''

    set @strg = '
    with cte as (

    SELECT  * , ROW_NUMBER() OVER(partition by PK ORDER BY UpdateDate) as RowNbr
    FROM    (

        SELECT  *
        FROM    [' + @schemaname + '].[' + @tableName + ']

        UNION

        SELECT  *
        FROM    [' + @schemaname + '].[' + @tableName + '_archive]

        ) a

    )
    '


    SET @strg = @strg + '

    SELECT  a.pk, a.updateDate, 
    CASE '

    DECLARE @colName varchar(255)
    DECLARE cur CURSOR FOR
        SELECT  COLUMN_NAME
        FROM    INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_SCHEMA = @schemaname
        AND TABLE_NAME = @tableName
        AND COLUMN_NAME NOT IN ('PK', 'Updatedate')

    OPEN cur
    FETCH NEXT FROM cur INTO @colName 

    WHILE @@FETCH_STATUS = 0
    BEGIN

        SET @colNameStrg  = @colNameStrg  + ' when a.' + @colName + ' <> b.' + @colName + ' then ''' + @colName + ''' '
        SET @oldValueString = @oldValueString + ' when a.' + @colName + ' <> b.' + @colName + ' then cast(a.' + @colName + ' as varchar(max))'
        SET @newValueString = @newValueString + ' when a.' + @colName + ' <> b.' + @colName + ' then cast(b.' + @colName + ' as varchar(max))'


    FETCH NEXT FROM cur INTO @colName 
    END

    CLOSE cur
    DEALLOCATE cur


    SET @colNameStrg = @colNameStrg  + '    END as ColumnChanges '
    SET @oldValueString = 'CASE ' + @oldValueString + ' END as OldValue'
    SET @newValueString = 'CASE ' + @newValueString + ' END as NewValue'

    SET @strg = @strg + @colNameStrg + ',' + @oldValueString + ',' + @newValueString

    SET @strg = @strg + '
        FROM    cte a join cte b on a.PK = b.PK and a.RowNbr + 1 = b.RowNbr 
        ORDER BY  a.pk, a.UpdateDate
    '

    print @strg

    execute sp_executesql @strg


    go

Contoh Panggilan:

exec getTableChanges 'dbo', 'bigTable'
Dharmendar Kumar 'DK'
sumber
Jika saya tidak salah, ini tidak menangkap beberapa perubahan yang dilakukan pada baris yang sama bukan?
Mikael Eriksson
itu benar .. beberapa kolom diperbarui pada saat yang sama tidak akan ditangkap. hanya kolom pertama dengan perubahan yang akan diambil.
Dharmendar Kumar 'DK'
1

Saya menggunakan AdventureWorks2012`, Production.ProductCostHistory dan Production.ProductListPriceHistory dalam contoh saya. Mungkin ini bukan contoh tabel histori yang sempurna, "tetapi skrip dapat menyatukan output keinginan dan output yang benar".

     DECLARE @sql NVARCHAR(MAX)
    ,@columns NVARCHAR(Max)
    ,@table VARCHAR(200) = 'ProductCostHistory'
    ,@Schema VARCHAR(200) = 'Production'
    ,@Archivecolumns NVARCHAR(Max)
    ,@ColForUnpivot NVARCHAR(Max)
    ,@ArchiveColForUnpivot NVARCHAR(Max)
    ,@PKCol VARCHAR(200) = 'ProductID'
    ,@UpdatedCol VARCHAR(200) = 'modifiedDate'
    ,@Histtable VARCHAR(200) = 'ProductListPriceHistory'
SELECT @columns = STUFF((
            SELECT ',CAST(p.' + QUOTENAME(column_name) + ' AS VARCHAR(MAX)) AS ' + QUOTENAME(column_name)
            FROM information_schema.columns
            WHERE table_name = @table
                AND column_name NOT IN (
                    @PKCol
                    ,@UpdatedCol
                    )
            ORDER BY ORDINAL_POSITION
            FOR XML PATH('')
            ), 1, 1, '')
    ,@Archivecolumns = STUFF((
            SELECT ',CAST(p1.' + QUOTENAME(column_name) + ' AS VARCHAR(MAX)) AS ' + QUOTENAME('A_' + column_name)
            FROM information_schema.columns
            WHERE table_name = @Histtable
                AND column_name NOT IN (
                    @PKCol
                    ,@UpdatedCol
                    )
            ORDER BY ORDINAL_POSITION
            FOR XML PATH('')
            ), 1, 1, '')
    ,@ColForUnpivot = STUFF((
            SELECT ',' + QUOTENAME(column_name)
            FROM information_schema.columns
            WHERE table_name = @table
                AND column_name NOT IN (
                    @PKCol
                    ,@UpdatedCol
                    )
            ORDER BY ORDINAL_POSITION
            FOR XML PATH('')
            ), 1, 1, '')
    ,@ArchiveColForUnpivot = STUFF((
            SELECT ',' + QUOTENAME('A_' + column_name)
            FROM information_schema.columns
            WHERE table_name = @Histtable
                AND column_name NOT IN (
                    @PKCol
                    ,@UpdatedCol
                    )
            ORDER BY ORDINAL_POSITION
            FOR XML PATH('')
            ), 1, 1, '')

--SELECT @columns   ,@Archivecolumns    ,@ColForUnpivot
SET @sql = N' 
    SELECT ' + @PKCol + ', ColumnName,
            OldValue,NewValue,' + @UpdatedCol + '
    FROM    (  
    SELECT p.' + @PKCol + '
        ,p.' + @UpdatedCol + '
        ,' + @columns + '
        ,' + @Archivecolumns + '
    FROM ' + @Schema + '.' + @table + ' p
    left JOIN ' + @Schema + '.' + @Histtable + ' p1 ON p.' + @PKCol + ' = p1.' + @PKCol + '

  ) t
    UNPIVOT (
        OldValue
        FOR ColumnName in (' + @ColForUnpivot + ')
    ) up

     UNPIVOT (
        NewValue
        FOR ColumnName1 in (' + @ArchiveColForUnpivot + ')
    ) up1

--print @sql
EXEC (@sql)

Di sini, di bagian dalam Pilih kueri, anggap p sebagai Tabel Utama dan p1 sebagai tabel Riwayat. Dalam unpivot, penting untuk mengubahnya menjadi tipe yang sama.

Anda dapat mengambil nama tabel lainnya dengan nama kolom yang lebih sedikit untuk memahami skrip saya. Setiap Penjelasan perlu dilakukan ping.

KumarHarsh
sumber