Dynamically select distinct current and previous columns from a sql table

一笑奈何 提交于 2019-12-01 07:26:02

问题


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


回答1:


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



回答2:


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 


来源:https://stackoverflow.com/questions/38617690/dynamically-select-distinct-current-and-previous-columns-from-a-sql-table

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!