Truy vấn chi tiết sự khác biệt giữa các hàng cho một lượng lớn dữ liệu


15

Tôi có một số bảng lớn, mỗi bảng có> 300 cột. Ứng dụng tôi đang sử dụng tạo "lưu trữ" các hàng đã thay đổi bằng cách tạo một bản sao của hàng hiện tại trong một bảng phụ.

Hãy xem xét một ví dụ tầm thường:

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

Bảng lưu trữ:

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

Trước khi thực hiện bất kỳ cập nhật nào dbo.bigtable, một bản sao của hàng được tạo dbo.bigtable_archive, sau đó dbo.bigtable.UpdateDateđược cập nhật với ngày hiện tại.

Do đó, kết UNIONhợp hai bảng với nhau và nhóm bằng cách PKtạo một dòng thời gian thay đổi, khi được sắp xếp theo UpdateDate.

Tôi muốn tạo một báo cáo chi tiết về sự khác biệt giữa các hàng, được sắp xếp theo UpdateDate, được nhóm theo PK, theo định dạng sau:

PK,   UpdateDate,  ColumnName,  Old Value,   New Value

Old ValueNew Valuecó thể là các cột có liên quan được truyền tới một VARCHAR(MAX)(không có TEXThoặc có BYTEcột liên quan), vì tôi không cần phải xử lý hậu kỳ các giá trị.

Hiện tại tôi không thể nghĩ ra một cách lành mạnh để làm điều này cho một số lượng lớn các cột, mà không dùng đến việc tạo các truy vấn theo chương trình - tôi có thể phải làm điều này.

Mở ra rất nhiều ý tưởng, vì vậy tôi sẽ thêm một phần thưởng cho câu hỏi sau 2 ngày.

Câu trả lời:


15

Điều này sẽ không có vẻ tốt, đặc biệt là với hơn 300 cột và không có sẵn LAG, cũng không có khả năng thực hiện quá tốt, nhưng giống như một cái gì đó để bắt đầu, tôi sẽ thử cách tiếp cận sau:

  • UNION Hai cái bàn.
  • Đối với mỗi PK trong tập hợp kết hợp, hãy lấy "hóa thân" trước đó từ bảng lưu trữ (cách thực hiện bên dưới sử dụng OUTER APPLY+ TOP (1)như một người nghèo LAG).
  • Truyền từng cột dữ liệu tới varchar(max)và hủy xoay chúng theo cặp, tức là giá trị hiện tại và giá trị trước đó ( CROSS APPLY (VALUES ...)hoạt động tốt cho thao tác này).
  • Cuối cùng, lọc kết quả dựa trên việc các giá trị trong mỗi cặp có khác nhau không.

Transact-SQL ở trên như tôi thấy:

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
;

13

Nếu bạn hủy kết nối dữ liệu vào bảng tạm thời

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

Bạn có thể phù hợp với các hàng để tìm giá trị mới và cũ với một tự tham gia vào PK, ColumnNameVersion = Version + 1.

Tất nhiên, phần không đẹp là thực hiện việc không xoay vòng 300 cột của bạn vào bảng tạm thời từ hai bảng cơ sở.

XML để giải cứu để làm cho mọi thứ bớt khó xử.

Có thể hủy liên kết dữ liệu với XML mà không cần phải biết những cột thực tế nào có trong bảng sẽ không được xoay vòng. Tên cột phải hợp lệ làm tên thành phần trong XML nếu không sẽ bị lỗi.

Ý tưởng là tạo một XML cho mỗi hàng có tất cả các giá trị cho hàng đó.

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 xsinilcó để tạo các yếu tố cho các cột với NULL.

XML sau đó có thể được băm nhỏ bằng cách sử dụng nodes('*') để có một hàng cho mỗi cột và sử dụng local-name(.)để lấy tên phần tử và text()nhận giá trị.

  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)

Giải pháp đầy đủ dưới đây. Lưu ý rằng Versionđược đảo ngược. 0 = Phiên bản cuối cùng.

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;

6

Tôi muốn đề nghị bạn một cách tiếp cận khác.

Mặc dù bạn không thể thay đổi ứng dụng hiện tại, có thể bạn có thể thay đổi hành vi cơ sở dữ liệu.

Nếu có thể, tôi sẽ thêm hai TRIGGERS vào các bảng hiện tại.

Một INSTEAD OF INSERT trên dbo.bigtable_archive chỉ thêm bản ghi mới nếu nó không tồn tại.

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

Và một trình kích hoạt SAU KHI trên bigtable thực hiện chính xác cùng một công việc, nhưng sử dụng dữ liệu của 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

Ok, tôi đã thiết lập một ví dụ nhỏ ở đây với các giá trị ban đầu này:

SELECT * FROM bigtable;
SELECT * FROM bigtable_archive;
Cập nhật ngày | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
02/02 00:00:00 | ABC | C3 | 1 | C1  

Cập nhật ngày | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
01 ngộ 00:00:00 | ABC | C1 | 1 | C1  

Bây giờ bạn nên chèn vào bigtable_archive tất cả các bản ghi đang chờ xử lý từ bigtable.

INSERT INTO bigtable_archive
SELECT *
FROM   bigtable
WHERE  UpdateDate >= '20170102';
SELECT * FROM bigtable_archive;
GO
Cập nhật ngày | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
01 ngộ 00:00:00 | ABC | C1 | 1 | C1  
02/02 00:00:00 | ABC | C3 | 1 | C1  

Bây giờ, lần tiếp theo ứng dụng cố gắng chèn một bản ghi trên bảng bigtable_archive, các trình kích hoạt sẽ phát hiện nếu nó tồn tại và việc chèn sẽ được tránh.

INSERT INTO dbo.bigtable_archive VALUES('20170102', 'ABC', 'C3', 1, 'C1');
GO
SELECT * FROM bigtable_archive;
GO
Cập nhật ngày | PK | col1 | col2 | col3
: ------------------ | : - | : --- | ---: | : ---
01 ngộ 00:00:00 | ABC | C1 | 1 | C1  
02/02 00:00:00 | ABC | C3 | 1 | C1  

Rõ ràng bây giờ bạn có thể nhận được dòng thời gian thay đổi bằng cách chỉ truy vấn bảng lưu trữ. Và ứng dụng sẽ không bao giờ nhận ra rằng một triggger đang lặng lẽ thực hiện công việc dưới vỏ bọc.

dbfiddle ở đây


4

Đề xuất làm việc, với một số dữ liệu mẫu, có thể được tìm thấy @ rextester: untable unpOLL


Ý chính của hoạt động:

1 - Sử dụng syscolumncho xml để tự động tạo danh sách cột của chúng tôi cho hoạt động hủy trục; tất cả các giá trị sẽ được chuyển đổi thành varchar (max), w / NULL được chuyển đổi thành chuỗi 'NULL' (địa chỉ này có vấn đề với việc bỏ qua các giá trị NULL)

2 - Tạo truy vấn động để hủy dữ liệu vào bảng tạm thời #columns

  • Tại sao một bảng temp vs CTE (thông qua với khoản)? liên quan đến vấn đề hiệu suất tiềm năng đối với một khối lượng lớn dữ liệu và tự tham gia CTE mà không có sơ đồ băm / chỉ mục có thể sử dụng được; một bảng tạm thời cho phép tạo ra một chỉ mục giúp cải thiện hiệu suất khi tự tham gia [xem tự tham gia CTE chậm ]
  • Dữ liệu được ghi vào #column theo thứ tự PK + ColName + UpdateDate, cho phép chúng tôi lưu trữ các giá trị PK / Colname trong các hàng liền kề; một cột danh tính ( thoát ) cho phép chúng ta tự tham gia các hàng liên tiếp này thông qua Rid = Rid + 1

3 - Thực hiện tự tham gia bảng #temp để tạo đầu ra mong muốn

Cắt-n-dán từ rextester ...

Tạo một số dữ liệu mẫu và bảng #columns của chúng tôi:

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)
);

Ruột của giải pháp:

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
;

Và kết quả:

nhập mô tả hình ảnh ở đây

Lưu ý: xin lỗi ... không thể tìm ra một cách dễ dàng để cắt-n-dán đầu ra rextester vào một khối mã. Tôi đang mở để đề xuất.


Các vấn đề / mối quan tâm tiềm năng:

1 - chuyển đổi dữ liệu thành một varchar chung (tối đa) có thể dẫn đến mất độ chính xác của dữ liệu, điều này có thể có nghĩa là chúng ta bỏ lỡ một số thay đổi dữ liệu; hãy xem xét các cặp datetime và float sau đây, khi được chuyển đổi / chuyển thành 'varchar (max)' chung, sẽ mất độ chính xác (nghĩa là các giá trị được chuyển đổi là như nhau):

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

Mặc dù độ chính xác của dữ liệu có thể được duy trì, nó sẽ yêu cầu mã hóa nhiều hơn một chút (ví dụ: truyền dựa trên kiểu dữ liệu cột nguồn); hiện tại tôi đã chọn gắn bó với phương sai chung (tối đa) theo khuyến nghị của OP (và giả định rằng OP biết rõ dữ liệu đủ để biết rằng chúng tôi sẽ không gặp phải bất kỳ vấn đề nào về mất độ chính xác dữ liệu).

2 - đối với các tập dữ liệu thực sự lớn, chúng tôi có nguy cơ thổi bay một số tài nguyên máy chủ, cho dù đó là không gian tempdb và / hoặc bộ nhớ cache / bộ nhớ; vấn đề chính xuất phát từ vụ nổ dữ liệu xảy ra trong một lần không xoay vòng (ví dụ: chúng tôi chuyển từ 1 hàng và 302 mẩu dữ liệu sang 300 hàng và 1200-1500 mẩu dữ liệu, bao gồm 300 bản sao của cột PK và UpdateDate, 300 tên cột)


1

Cách tiếp cận này sử dụng truy vấn động để tạo ra một sql để có được những thay đổi. SP lấy tên bảng & lược đồ và đưa ra đầu ra mà bạn mong muốn.

Các giả định là các cột PK và UpdateDate có mặt trong tất cả các bảng. Và tất cả các bảng lưu trữ có định dạng gốcTableName + "_archive" ..

NB: Tôi đã không kiểm tra nó cho hiệu suất.

NB: vì điều này sử dụng sql động, tôi nên thêm cảnh báo về bảo mật / sql tiêm. Hạn chế quyền truy cập vào SP và thêm các xác nhận khác để ngăn chặn tiêm 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

Cuộc gọi mẫu:

exec getTableChanges 'dbo', 'bigTable'

Nếu tôi không nhầm, điều này không bắt được nhiều thay đổi được thực hiện cho cùng một hàng phải không?
Mikael Eriksson

đúng vậy .. nhiều cột được cập nhật cùng một lúc sẽ không bị bắt. chỉ cột đầu tiên có thay đổi sẽ được ghi lại.
DharmWiki Kumar 'DK'

1

Tôi đang sử dụng AdventureWorks2012`, Sản xuất. ProducttCostHistory và Sản xuất. ProducttListpriceHistory trong ví dụ của tôi. Đây có thể không phải là ví dụ bảng lịch sử hoàn hảo, "nhưng tập lệnh có thể kết hợp đầu ra mong muốn và đầu ra chính xác".

     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)

Ở đây trong bên trong Chọn truy vấn coi p là Bảng chính và p1 là bảng Lịch sử. Trong việc không xoay vòng, điều quan trọng là phải chuyển đổi nó thành cùng loại.

Bạn có thể lấy bất kỳ tên bảng nào khác có ít tên cột hơn để hiểu tập lệnh của tôi. Bất kỳ giải thích nào cần ping tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.