많은 양의 데이터에 대한 행 간 차이를 자세히 쿼리


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_archivedbo.bigtable.UpdateDate현재 날짜로 업데이트됩니다.

따라서 UNION두 테이블을 함께 그룹화하여 그룹화 PK하면 순서에 따라 변경 타임 라인이 생성됩니다 UpdateDate.

내가 주문한 행 사이의 차이를 자세히 설명하는 보고서를 생성 할 UpdateDate별로 그룹화, PK다음과 같은 형식을 :

PK,   UpdateDate,  ColumnName,  Old Value,   New Value

Old Value그리고 New ValueA를 캐스팅 관련 열 수 있습니다 VARCHAR(MAX)(NO 있습니다 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.

물론 그렇지 않은 부분은 물론 두 개의 기본 테이블에서 임시 테이블로 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 xsinil로 열에 대한 요소를 만들 수 있습니까 NULL?

그런 다음 nodes('*') 각 열에 대해 하나의 행을 가져오고 local-name(.)get 요소 이름을 사용 text()하고 값을 얻는 데 XML을 파쇄 할 수 있습니다 .

  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를 추가합니다.

dbo.bigtable_archive의 INSTEAD OF INSERT 중 하나가 존재하지 않는 경우에만 새 레코드를 추가합니다.

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

bigtable의 AFTER INSERT 트리거는 정확히 동일한 작업을 수행하지만 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;
업데이트 날짜 | PK | col1 | col2 | col3
: ------------------ | :-| : --- | --- : | : ---
02/01/2017 00:00:00 | ABC | C3 | 1 | C1  

업데이트 날짜 | PK | col1 | col2 | col3
: ------------------ | :-| : --- | --- : | : ---
01/01/2017 00:00:00 | ABC | C1 | 1 | C1  

이제 bigtable에서 모든 보류중인 레코드를 bigtable_archive에 삽입해야합니다.

INSERT INTO bigtable_archive
SELECT *
FROM   bigtable
WHERE  UpdateDate >= '20170102';
SELECT * FROM bigtable_archive;
GO
업데이트 날짜 | 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
업데이트 날짜 | 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

일부 샘플 데이터가 포함 된 작업 제안서는 다음 위치에서 찾을 수 있습니다. 에서 Bigtable UNPIVOT


작업의 요지 :

1- syscolumnsXML 사용 에 대해 피벗 해제 작업에 대한 열 목록을 동적으로 생성하십시오. 모든 값은 varchar (max)로 변환되고 NULL은 문자열 'NULL'로 변환됩니다 (이 값은 피벗되지 않은 NULL 값을 건너 뛰는 문제를 해결 함)

2-#columns 임시 테이블로 데이터를 피벗 해제하기위한 동적 쿼리 생성

  • 왜 (를 통해 CTE 대 임시 테이블 절)? 사용 가능한 인덱스 / 해싱 방식이없는 대량의 데이터 및 CTE 자체 조인에 대한 잠재적 인 성능 문제와 관련이 있습니다. 임시 테이블을 사용하면 자체 조인의 성능을 향상시켜야하는 인덱스를 만들 수 있습니다. [ 느린 CTE 자체 조인 참조 ].
  • PK + ColName + UpdateDate 순서로 # 열에 데이터가 기록되므로 PK / Colname 값을 인접한 행에 저장할 수 있습니다. 식별 열 ( rid )을 사용하면 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
;

그리고 결과 :

여기에 이미지 설명을 입력하십시오

참고 : 사과 ...는 rextester 출력을 코드 블록으로 잘라 붙여 넣을 수있는 쉬운 방법을 알 수 없었습니다. 나는 제안에 개방적이다.


잠재적 인 문제 / 관심 :

1-일반 varchar (max)로 데이터를 변환하면 데이터 정밀도가 손실 될 수 있으며 이는 데이터 변경 사항이 일부 누락되었음을 의미합니다. 일반 'varchar (max)'로 변환 / 캐스트 할 때 정밀도를 잃는 (즉, 변환 된 값이 동일 함) 다음 날짜 시간 및 부동 쌍을 고려하십시오.

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

데이터 정밀도는 유지 될 수 있지만 약간 더 많은 코딩이 필요합니다 (예 : 소스 열 데이터 유형에 기반한 캐스팅). 지금은 OP의 권장 사항 (및 OP가 데이터 정밀도 손실 문제에 빠지지 않을 정도로 데이터를 잘 알고 있다고 가정)에 따라 일반적인 varchar (max)를 고수하기로 결정했습니다.

2-실제로 큰 데이터 집합의 경우 tempdb 공간 및 / 또는 캐시 / 메모리 여부에 관계없이 일부 서버 리소스가 폭발 할 위험이 있습니다. 주요 문제는 피벗 해제 중 발생하는 데이터 폭발 (예 : PK 및 UpdateDate 열 300 개, 열 이름 300 개를 포함하여 1 행 302 개 데이터에서 300 행 1, 1200-1500 개 데이터로 이동)에서 발생합니다.


1

이 방법은 동적 쿼리를 사용하여 변경 사항을 가져 오기 위해 SQL을 생성합니다. SP는 테이블 및 스키마 이름을 사용하여 원하는 출력을 제공합니다.

PK 및 UpdateDate 열이 모든 테이블에 있다고 가정합니다. 그리고 모든 아카이브 테이블의 형식은 originalTableName + "_archive"입니다.

NB : 성능을 확인하지 않았습니다.

NB : 동적 SQL을 사용하기 때문에 보안 / SQL 주입에 대한주의를 기울여야합니다. 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 '

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은 기록 테이블로 간주합니다. 피벗 해제에서는 동일한 유형으로 변환하는 것이 중요합니다.

내 스크립트를 이해하기 위해 열 이름이 적은 다른 테이블 이름을 사용할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.