สอบถามรายละเอียดความแตกต่างระหว่างแถวสำหรับข้อมูลจำนวนมาก


15

ฉันมีตารางขนาดใหญ่จำนวนมากแต่ละอันมีคอลัมน์> 300 แอปพลิเคชันที่ฉันใช้สร้าง "ที่เก็บถาวร" ของแถวที่เปลี่ยนแปลงโดยการทำสำเนาแถวปัจจุบันในตารางที่สอง

ลองพิจารณาตัวอย่างเล็ก ๆ น้อย ๆ :

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

ตารางเก็บถาวร:

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

ก่อนที่จะดำเนินการอัปเดตใด ๆdbo.bigtableสำเนาของแถวจะถูกสร้างขึ้นdbo.bigtable_archiveจากนั้นdbo.bigtable.UpdateDateอัปเดตด้วยวันที่ปัจจุบัน

ดังนั้นUNIONไอเอ็นจีตารางทั้งสองเข้าด้วยกันและการจัดกลุ่มโดยจะสร้างระยะเวลาของการเปลี่ยนแปลงเมื่อได้รับคำสั่งจากPKUpdateDate

ฉันต้องการสร้างรายงานรายละเอียดความแตกต่างระหว่างแถวเรียงลำดับโดยUpdateDateจัดกลุ่มตามPKในรูปแบบต่อไปนี้:

PK,   UpdateDate,  ColumnName,  Old Value,   New Value

Old ValueและNew Valueสามารถเป็นคอลัมน์ที่เกี่ยวข้องกับVARCHAR(MAX)(มีTEXTหรือไม่มีBYTEคอลัมน์ที่เกี่ยวข้อง) เนื่องจากฉันไม่จำเป็นต้องทำการประมวลผลค่าเหล่านั้นด้วยตนเอง

ในขณะนี้ฉันไม่สามารถคิดวิธีที่มีเหตุผลในการทำเช่นนี้สำหรับคอลัมน์จำนวนมากโดยไม่ต้องหันไปใช้การสร้างแบบสอบถามโดยทางโปรแกรม - ฉันอาจต้องทำสิ่งนี้

เปิดรับแนวคิดมากมายดังนั้นฉันจะเพิ่มค่าหัวให้กับคำถามหลังจาก 2 วัน

คำตอบ:


15

สิ่งนี้จะไม่ดูสวยโดยเฉพาะอย่างยิ่งเมื่อมีคอลัมน์มากกว่า 300 คอลัมน์และไม่มีความพร้อมและไม่มีLAGแนวโน้มที่จะทำงานได้ดี แต่เป็นสิ่งที่เริ่มต้นฉันจะลองใช้วิธีต่อไปนี้:

  • UNION สองตาราง
  • สำหรับ PK แต่ละตัวในชุดที่รวมกันให้รับ "การจุติมาก่อน" จากตารางเก็บถาวร (การนำไปใช้ด้านล่างใช้OUTER APPLY+ TOP (1)ว่าเป็นคนยากจนLAG)
  • แคสต์แต่ละคอลัมน์ข้อมูลvarchar(max)และยกเลิกการหมุนเป็นคู่เช่นค่าปัจจุบันและค่าก่อนหน้า ( CROSS APPLY (VALUES ...)ทำงานได้ดีสำหรับการดำเนินการนี้)
  • สุดท้ายกรองผลลัพธ์ตามค่าในแต่ละคู่ที่แตกต่างกัน

Transact-SQL จากที่กล่าวมา:

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

หากคุณยกเลิกการหมุนข้อมูลไปยังตารางชั่วคราว

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

คุณอาจไม่ตรงกับแถวที่จะหาค่าทั้งเก่าและใหม่ด้วยตัวเองเข้าร่วมในPK, และColumnNameVersion = Version + 1

แน่นอนว่าส่วนที่ไม่ค่อยสวยนั้นคือการทำ unpivot ของคอลัมน์ 300 ของคุณลงในตารางชั่วคราวจากสองตารางฐาน

XML เพื่อช่วยเหลือเพื่อทำให้สิ่งต่าง ๆ น้อยลง

มีความเป็นไปได้ที่จะยกเลิกการทำข้อมูลด้วย XML โดยไม่ต้องรู้ว่าคอลัมน์ใดที่มีอยู่จริงในตารางที่จะไม่ถูกตรึง ชื่อคอลัมน์จะต้องถูกต้องเป็นชื่อองค์ประกอบใน XML หรือจะล้มเหลว

แนวคิดคือการสร้าง XML หนึ่งรายการสำหรับแต่ละแถวที่มีค่าทั้งหมดสำหรับแถวนั้น

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 xsinilNULLจะมีการสร้างองค์ประกอบสำหรับคอลัมน์ที่มี

จากนั้น XML สามารถถูกทำลายโดยใช้nodes('*') เพื่อรับหนึ่งแถวสำหรับแต่ละคอลัมน์และใช้local-name(.)เพื่อรับชื่อองค์ประกอบและtext()เพื่อรับค่า

  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)

วิธีการแก้ปัญหาด้านล่างเต็ม โปรดทราบว่าVersionจะกลับรายการ 0 = รุ่นล่าสุด

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

ฉันขอแนะนำวิธีการอื่น

แม้ว่าคุณจะไม่สามารถเปลี่ยนแอปพลิเคชันปัจจุบันได้ แต่คุณสามารถเปลี่ยนพฤติกรรมของฐานข้อมูลได้

ถ้าเป็นไปได้ฉันจะเพิ่ม TRIGGERS สองตัวในตารางปัจจุบัน

หนึ่ง INSTEAD OF INSERT บน dbo.bigtable_archive ที่เพิ่มระเบียนใหม่เฉพาะในกรณีที่ไม่มีอยู่ในปัจจุบัน

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

และ AFTER INSERT จะทริกเกอร์บน bigtable ที่ทำงานเดียวกัน แต่ใช้ข้อมูลของ 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

ตกลงฉันได้ตั้งตัวอย่างเล็ก ๆที่นี่ด้วยค่าเริ่มต้นนี้:

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

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

ตอนนี้คุณควรแทรกลงใน bigtable_archive ระเบียนที่ค้างอยู่ทั้งหมดจาก bigtable

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

ตอนนี้ในครั้งถัดไปที่แอปพลิเคชันพยายามแทรกระเบียนในตาราง bigtable_archive ทริกเกอร์จะตรวจพบว่ามีอยู่หรือไม่และจะหลีกเลี่ยงการแทรก

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

แน่นอนตอนนี้คุณสามารถรับไทม์ไลน์ของการเปลี่ยนแปลงได้โดยการสืบค้นเฉพาะตารางเก็บถาวร และแอปพลิเคชันจะไม่มีทางรู้ได้เลยว่าทริกเกอร์ทำงานอย่างเงียบ ๆ ภายใต้การคุ้มครอง

dbfiddle ที่นี่


4

ข้อเสนอการทำงานพร้อมด้วยข้อมูลตัวอย่างบางส่วนสามารถพบได้ที่ @ rextester: bigtable unpivot


ส่วนสำคัญของการดำเนินการ:

1 - ใช้syscolumnsและสำหรับ xmlเพื่อสร้างรายการคอลัมน์ของเราแบบไดนามิกสำหรับการดำเนินการยกเลิกการเปลี่ยนแปลง ค่าทั้งหมดจะถูกแปลงเป็น varchar (สูงสุด), w / NULL ที่ถูกแปลงเป็นสตริง 'NULL' (ปัญหานี้อยู่ด้วยการยกเลิกการข้ามค่า NULL)

2 - สร้างคิวรีแบบไดนามิกเพื่อยกเลิกการโอนข้อมูลลงในตาราง #columns temp

  • ทำไมตารางเทมเพลตเทียบกับ CTE (ผ่านด้วยประโยค)? เกี่ยวข้องกับปัญหาประสิทธิภาพที่อาจเกิดขึ้นสำหรับข้อมูลจำนวนมากและการเข้าร่วม CTE ด้วยตนเองโดยไม่มีดัชนี / hashing ที่ใช้งานได้ ตาราง temp ช่วยให้สามารถสร้างดัชนีซึ่งควรปรับปรุงประสิทธิภาพในการเข้าร่วมด้วยตนเอง [ดูการเข้าร่วม CTE แบบช้าด้วยตนเอง ]
  • ข้อมูลถูกเขียนไปยัง #columns ในลำดับ PK + ColName + UpdateDate ทำให้เราสามารถจัดเก็บค่า PK / Colname ในแถวที่อยู่ติดกัน คอลัมน์ข้อมูลประจำตัว ( กำจัด ) ช่วยให้เราสามารถเข้าร่วมแถวต่อเนื่องเหล่านี้ด้วยตนเองผ่านrid = rid + 1

3 - ทำการรวมตัวเองของตาราง #temp เพื่อสร้างเอาต์พุตที่ต้องการ

การตัดวางจาก rextester ...

สร้างข้อมูลตัวอย่างและตาราง #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
;

และผลลัพธ์:

ป้อนคำอธิบายรูปภาพที่นี่

หมายเหตุ: ขออภัย ... ไม่สามารถหาวิธีที่ง่ายในการตัด -n-paste เอาต์พุต rextester ลงในบล็อกโค้ด ฉันเปิดรับข้อเสนอแนะ


ปัญหา / ข้อกังวลที่อาจเกิดขึ้น:

1 - การแปลงข้อมูลเป็น varchar ทั่วไป (สูงสุด) สามารถนำไปสู่การสูญเสียความแม่นยำของข้อมูลซึ่งหมายความว่าเราอาจพลาดการเปลี่ยนแปลงข้อมูลบางอย่าง พิจารณาวันที่และเวลาและคู่ลอยที่เมื่อแปลง / ส่งไปยัง 'varchar (สูงสุด)' ทั่วไปจะสูญเสียความแม่นยำ (เช่นค่าที่แปลงแล้วจะเท่ากัน):

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

ในขณะที่ความแม่นยำของข้อมูลสามารถรักษาได้นั้นจะต้องใช้การเข้ารหัสอีกเล็กน้อย (เช่นการคัดเลือกจากฐานข้อมูลคอลัมน์แหล่งข้อมูล) สำหรับตอนนี้ฉันเลือกที่จะใช้ varchar ทั่วไป (สูงสุด) ตามคำแนะนำของ OP (และสมมติว่า OP รู้ข้อมูลดีพอที่จะรู้ว่าเราจะไม่พบปัญหาการสูญเสียความแม่นยำของข้อมูล)

2 - สำหรับชุดข้อมูลขนาดใหญ่จริง ๆ เราเสี่ยงต่อการระเบิดทรัพยากรเซิร์ฟเวอร์ไม่ว่าจะเป็นพื้นที่ tempdb และ / หรือแคช / หน่วยความจำ ปัญหาหลักมาจากการระเบิดของข้อมูลที่เกิดขึ้นในช่วง unpivot (เช่นเราไปจาก 1 แถวและ 302 ชิ้นส่วนของข้อมูลเป็น 300 แถวและ 1200-1500 ชิ้นส่วนของข้อมูลรวมถึง 300 สำเนาของคอลัมน์ PK และ UpdateDate ชื่อคอลัมน์ 300 รายการ)


1

วิธีนี้ใช้การสืบค้นแบบไดนามิกเพื่อสร้าง sql เพื่อรับการเปลี่ยนแปลง SP ใช้ชื่อตารางและสคีมาและให้ผลลัพธ์ที่คุณต้องการ

สมมติฐานคือคอลัมน์ PK และ UpdateDate แสดงอยู่ในตารางทั้งหมด และตารางเก็บถาวรทั้งหมดมีรูปแบบ originalTableName + "_archive"

หมายเหตุ: ฉันยังไม่ได้ตรวจสอบเพื่อประสิทธิภาพ

หมายเหตุ: ตั้งแต่นี้ใช้ไดนามิก sql ฉันควรเพิ่มข้อแม้เกี่ยวกับความปลอดภัย / sql injection จำกัด การเข้าถึง SP และเพิ่มการตรวจสอบอื่น ๆ เพื่อป้องกันการฉีด 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

โทรตัวอย่าง:

exec getTableChanges 'dbo', 'bigTable'

หากฉันไม่เข้าใจผิดนี่จะไม่จับการเปลี่ยนแปลงหลายอย่างในแถวเดียวกันใช่ไหม?
Mikael Eriksson

ถูกต้อง .. คอลัมน์หลายคอลัมน์ที่ถูกอัพเดตพร้อมกันจะไม่ถูกบันทึก เฉพาะคอลัมน์แรกที่มีการเปลี่ยนแปลงเท่านั้นที่จะถูกบันทึก
Dharmendar Kumar 'DK'

1

ฉันกำลังใช้ AdventureWorks2012`, Production.ProductCostHistory และ Production.ProductListPriceHistory ในตัวอย่างของฉันมันอาจจะไม่ใช่ตัวอย่างตารางประวัติที่สมบูรณ์แบบ "แต่สคริปต์สามารถรวบรวมเอาท์พุทปรารถนาและเอาท์พุทที่ถูกต้อง"

     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)

ที่นี่ในแบบสอบถามแบบใช้เลือกข้อมูลให้พิจารณา p เป็นตารางหลักและ p1 เป็นตารางประวัติในการยกเลิกการหมุนเป็นสิ่งสำคัญที่จะต้องแปลงเป็นประเภทเดียวกัน

คุณสามารถใช้ชื่อตารางอื่น ๆ ที่มีชื่อคอลัมน์น้อยลงเพื่อทำความเข้าใจกับสคริปต์ของฉันคำอธิบายใด ๆ ที่จำเป็นแล้ว ping ฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.