Dynamically select distinct current and previous c

2019-02-18 12:58发布

I have a temporary table like so

   Id  |Name    |Status |   Rate |  Method  |ModifiedTime             |ModifiedBy
-----------------------------------------------------------------------------
    1  |Recipe1 |  0    |    30  |   xyz    | 2016-07-26 14:55:57.977 |     A
-------------------------------------------------------------------------------
    2  |Recipe1 |  0    |    30  |   abc    | 2016-07-26 14:56:18.123 |     A
--------------------------------------------------------------------------------
    3  |Recipe1 |  1    |    30  |   xyz    | 2016-07-26 14:57:50.180 |     b

I would like to select only the changes and wanted to show what the value was previously and what it is currently accompanied by who changed it. The final outcome will be as follows. I am using SQL Server 2014.

Item    | Before |  After |ModifiedTime             | ModifiedBy
-----------------------------------------------------------------------------
Method  |  xyz   |  Abc   | 2016-07-26 14:56:18.123 |   A
-------------------------------------------------------------------------------
Status  |  0     |  1     | 2016-07-26 14:57:50.180 |   b
--------------------------------------------------------------------------------
Method  |  Abc   |  xyz   | 2016-07-26 14:57:50.180 |   b

I would like to do this dynamically instead of specifying each column name individually as shown in this link

Link

2条回答
手持菜刀,她持情操
2楼-- · 2019-02-18 13:07

Ok, I adapted my previous answer but on Dynamic SQL. It's a little crazy but it works (using testTable as table name you can change that just replace 'testTable'):

DECLARE @query NVARCHAR(max) 

SET @query = 'select item, case item'; 

SELECT @query = @query + Stuff(( SELECT 
' when '''+a.NAME+''' then cast(prev'+a.NAME+' as varchar) ' 
FROM sys.all_columns a JOIN sys.tables t ON 
t.object_id = a.object_id AND t.NAME = 'testTable' AND a.NAME 
NOT IN ('id', 
'Name', 'ModifiedTime', 'ModifiedBy') FOR xml path('') ), 1, 0, 
''); 

SET @query = @query + ' end as Before, case item '; 

SELECT @query = @query + Stuff(( SELECT 
                       ' when '''+a.NAME+''' then cast('+a.NAME+' as varchar) ' 
                       FROM sys.all_columns a JOIN sys.tables t ON t.object_id = 
                       a.object_id AND t.NAME = 'testTable' AND a.NAME NOT IN ( 
                'id', 
                       'Name', 
                       'ModifiedTime', 'ModifiedBy') FOR xml path('') ), 1, 1, 
                ''); 

SET @query = @query 
             + ' end as After, ModifiedTime, ModifiedBy from ( select '; 

SELECT @query = @query + Stuff(( SELECT a.NAME +', lag('+ a.NAME + 
                       ') over (partition by Name order by id) prev'+a.NAME+', ' 
                FROM 
                       sys.all_columns a JOIN sys.tables t ON t.object_id = 
                a.object_id 
                       AND t.NAME = 'testTable' AND a.NAME NOT IN ('id', 'Name', 
                       'ModifiedTime', 'ModifiedBy') FOR xml path('') ), 1, 0, 
                ''); 

SET @query = @query 
             + '  ModifiedBy, ModifiedTime from testTable ) as t1 cross join  (' 
; 

SELECT @query = @query + Stuff(( SELECT ' select '''+ a.NAME + 
                '''as item union all ' 
                       FROM 
                       sys.all_columns a JOIN sys.tables t ON t.object_id = 
                a.object_id 
                       AND t.NAME = 
                       'testTable' AND a.NAME NOT IN ('id', 'Name', 
                'ModifiedTime', 
                       'ModifiedBy') FOR xml path('') ), 1, 1, ''); 

SET @query = LEFT(@query, Len(@query) - 10); --get rid of last union all 
SET @query = @query + ' ) items where '; 

SELECT @query = @query + Stuff(( SELECT ' or (item = '''+ a.NAME +''' and '+ 
                a.NAME + 
                       ' !=  prev'+ a.NAME +')' FROM sys.all_columns a JOIN 
                sys.tables 
                       t ON t.object_id = a.object_id AND t.NAME = 'testTable' 
                AND 
                       a.NAME NOT IN ('id', 'Name', 'ModifiedTime', 'ModifiedBy' 
                ) FOR 
                       xml 
                       path('') ), 1, 3, ''); 

SET @query = @query + ' order by ModifiedTime'; 

EXECUTE Sp_executesql 
  @query 
查看更多
SAY GOODBYE
3楼-- · 2019-02-18 13:21

Assuming NAME (Recipe1) is a key

Declare @Table table (Id int,Name varchar(50),Status int,Rate int,Method varchar(50),ModifiedTime DateTime,ModifiedBy varchar(50))
Insert Into @Table values
(1,'Recipe1',0,30,'xyz','2016-07-26 14:55:57.977','A'),
(2,'Recipe1',0,30,'abc','2016-07-26 14:56:18.123','A'),
(3,'Recipe1',1,30,'xyz','2016-07-26 14:57:50.180','b')

Declare @XML xml
Set @XML = (Select * from @Table for XML RAW)

;with cteBase as (
    Select ID           = r.value('@Id','int') 
          ,Name         = r.value('@Name','varchar(150)')
          ,ModifiedTime = r.value('@ModifiedTime','varchar(150)')
          ,ModifiedBy   = r.value('@ModifiedBy','varchar(150)')
          ,Item         = Attr.value('local-name(.)','varchar(max)')
          ,Value        = Attr.value('.','varchar(max)')
    From  @XML.nodes('/row') AS A(r)
    Cross Apply A.r.nodes('./@*[local-name(.)!="Id"]') AS B(Attr)
)
,cteExt as (Select *,LastValue =Lag(Value) over (Partition By Name,Item Order by ModifiedTime) From cteBase)
Select Name
      ,Item
      ,Before=LastValue
      ,After =Value
      ,ModifiedTime
      ,ModifiedBy
 From  cteExt 
 Where Value<>LastValue and LastValue is not null
   and Item not in ('ModifiedTime','ModifiedBy')
 Order By Name,ModifiedTime

Returns

Name    Item    Before  After   ModifiedTime            ModifiedBy
Recipe1 Method  xyz     abc     2016-07-26T14:56:18.123 A
Recipe1 Method  abc     xyz     2016-07-26T14:57:50.180 b
Recipe1 Status  0       1       2016-07-26T14:57:50.180 b
查看更多
登录 后发表回答