【sqlserver】在没有数据库备份的情况下,获得操作记录信息【code】

原文链接:How to recover modified records from SQL Server without Backup

SQL Server 2005或以上恢复修改的数据。(注:这个脚本可以恢复以下数据类型与CS整理兼容)。

  • tinyint
  • smallint
  • int
  • bigint
  • bit
  • char
  • varchar
  • nchar
  • nvarchar
  • datetime
  • smalldatetime
  • money
  • smallmoney
  • decimal
  • numeric
  • real
  • float
  • binary
  • varbinary
  • uniqueidentifier

让我解释这个问题演示简单的例子。

--Create Table
CREATE TABLE [dbo].[Student](  
      [Sno] [int] NOT NULL,  
      [Student ID] nvarchar(6) Not NULL ,  
      [Student name] [varchar](50) NOT NULL,  
      [Date of Birth]  datetime not null,  
      [Weight] [int] NULL)  
--Inserting data into table
Insert into dbo.[Student] values (1,'STD001','Bob','2003-12-31',40)  
Insert into dbo.[Student] values (2,'STD002','Alexander','2004-11-15',35) 
--Check the existence of the data
Select * from dbo.[Student]
 
Update [Student] Set [Student Name]='Bob jerry' --Where [SNO]=1 forget to use where clause
 
Select * from dbo.[Student]
 
存储过程代码:
  1 Create PROCEDURE Recover_Modified_Data_Proc
  2 @Database_Name NVARCHAR(MAX),
  3 @SchemaName_n_TableName NVARCHAR(MAX),
  4 @Date_From datetime='1900/01/01',
  5 @Date_To datetime ='9999/12/31'
  6 AS
  7 DECLARE @parms nvarchar(1024)
  8 DECLARE @Fileid INT
  9 DECLARE @Pageid INT
 10 DECLARE @Slotid INT
 11 DECLARE @RowLogContents0 VARBINARY(8000)
 12 DECLARE @RowLogContents1 VARBINARY(8000)
 13 DECLARE @RowLogContents3 VARBINARY(8000)
 14 DECLARE @RowLogContents3_Var VARCHAR(MAX)
 15  
 16 DECLARE @RowLogContents4 VARBINARY(8000)
 17 DECLARE @LogRecord VARBINARY(8000)
 18 DECLARE @LogRecord_Var VARCHAR(MAX)
 19  
 20 DECLARE @ConsolidatedPageID VARCHAR(MAX)
 21 Declare @AllocUnitID as bigint
 22 Declare @TransactionID as VARCHAR(MAX)
 23 Declare @Operation as VARCHAR(MAX)
 24 Declare @DatabaseCollation VARCHAR(MAX)
 25  
 26  
 27 /*  Pick The actual data
 28 */
 29 declare @temppagedata table
 30 (
 31 [ParentObject] sysname,
 32 [Object] sysname,
 33 [Field] sysname,
 34 [Value] sysname)
 35  
 36 declare @pagedata table
 37 (
 38 [Page ID] sysname,
 39 [AllocUnitId] bigint,
 40 [ParentObject] sysname,
 41 [Object] sysname,
 42 [Field] sysname,
 43 [Value] sysname)
 44  
 45  
 46     DECLARE Page_Data_Cursor CURSOR FOR
 47     /*We need to filter LOP_MODIFY_ROW,LOP_MODIFY_COLUMNS from log for modified records & Get its Slot No, Page ID & AllocUnit ID*/
 48     SELECT [PAGE ID],[Slot ID],[AllocUnitId]
 49     FROM    sys.fn_dblog(NULL, NULL)  
 50     WHERE   
 51     AllocUnitId IN
 52     (Select [Allocation_unit_id] from sys.allocation_units allocunits
 53     INNER JOIN sys.partitions partitions ON (allocunits.type IN (1, 3)  
 54     AND partitions.hobt_id = allocunits.container_id) OR (allocunits.type = 2 
 55     AND partitions.partition_id = allocunits.container_id)  
 56     Where object_id=object_ID('' + @SchemaName_n_TableName + ''))
 57     AND Operation IN ('LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS')  AND [Context] IN ('LCX_HEAP','LCX_CLUSTERED')
 58     /*Use this subquery to filter the date*/
 59  
 60     AND [TRANSACTION ID] IN (SELECT DISTINCT [TRANSACTION ID] FROM    sys.fn_dblog(NULL, NULL) 
 61     WHERE Context IN ('LCX_NULL') AND Operation in ('LOP_BEGIN_XACT')  
 62     AND [Transaction Name]='UPDATE'
 63     AND  CONVERT(NVARCHAR(11),[Begin Time]) BETWEEN @Date_From AND @Date_To)
 64  
 65     /****************************************/
 66  
 67     GROUP BY [PAGE ID],[Slot ID],[AllocUnitId]
 68     ORDER BY [Slot ID]    
 69   
 70     OPEN Page_Data_Cursor
 71  
 72     FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID, @Slotid,@AllocUnitID
 73  
 74     WHILE @@FETCH_STATUS = 0
 75     BEGIN
 76         DECLARE @hex_pageid AS VARCHAR(Max)
 77         /*Page ID contains File Number and page number It looks like 0001:00000130.
 78           In this example 0001 is file Number &  00000130 is Page Number & These numbers are in Hex format*/
 79         SET @Fileid=SUBSTRING(@ConsolidatedPageID,0,CHARINDEX(':',@ConsolidatedPageID)) -- Seperate File ID from Page ID
 80         SET @hex_pageid ='0x'+ SUBSTRING(@ConsolidatedPageID,CHARINDEX(':',@ConsolidatedPageID)+1,Len(@ConsolidatedPageID))  ---Seperate the page ID
 81         
 82         SELECT @Pageid=Convert(INT,cast('' AS XML).value('xs:hexBinary(substring(sql:variable("@hex_pageid"),sql:column("t.pos")) )', 'varbinary(max)')) -- Convert Page ID from hex to integer
 83         FROM (SELECT CASE substring(@hex_pageid, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos) 
 84                      
 85         DELETE @temppagedata
 86         -- Now we need to get the actual data (After modification) from the page
 87         INSERT INTO @temppagedata EXEC( 'DBCC PAGE(' + @DataBase_Name + ', ' + @fileid + ', ' + @pageid + ', 3) with tableresults,no_infomsgs;'); 
 88         -- Add Page Number and allocUnit ID in data to identity which one page it belongs to.                    
 89         INSERT INTO @pagedata SELECT @ConsolidatedPageID,@AllocUnitID,[ParentObject],[Object],[Field] ,[Value] FROM @temppagedata
 90  
 91         FETCH NEXT FROM Page_Data_Cursor INTO  @ConsolidatedPageID, @Slotid,@AllocUnitID
 92     END
 93  
 94 CLOSE Page_Data_Cursor
 95 DEALLOCATE Page_Data_Cursor
 96  
 97 DECLARE @Newhexstring VARCHAR(MAX);
 98  
 99  
100 DECLARE @ModifiedRawData TABLE
101 (
102   [ID] INT IDENTITY(1,1),
103   [PAGE ID] VARCHAR(MAX),
104   [Slot ID] INT,
105   [AllocUnitId] BIGINT,
106   [RowLog Contents 0_var] VARCHAR(MAX),
107   [RowLog Contents 0] VARBINARY(8000)
108 )
109 --The modified data is in multiple rows in the page, so we need to convert it into one row as a single hex value.
110 --This hex value is in string format
111 INSERT INTO @ModifiedRawData ([PAGE ID],[Slot ID],[AllocUnitId]
112 ,[RowLog Contents 0_var])
113 SELECT B.[PAGE ID],A.[Slot ID],A.[AllocUnitId]
114 ,(
115 SELECT REPLACE(STUFF((SELECT REPLACE(SUBSTRING([VALUE],CHARINDEX(':',[Value])+1,48),'','')
116 FROM @pagedata C  WHERE B.[Page ID]= C.[Page ID] And A.[Slot ID] =LTRIM(RTRIM(SUBSTRING(C.[ParentObject],5,3))) And [Object] Like '%Memory Dump%'
117 Group By [Value] FOR XML PATH('') ),1,1,'') ,' ','')
118 ) AS [Value]
119  
120 FROM sys.fn_dblog(NULL, NULL) A
121 INNER JOIN @pagedata B On A.[PAGE ID]=B.[PAGE ID]
122 AND A.[AllocUnitId]=B.[AllocUnitId]
123 AND A.[Slot ID] =LTRIM(RTRIM(Substring(B.[ParentObject],5,3)))
124 AND B.[Object] Like '%Memory Dump%'
125 WHERE A.AllocUnitId IN
126 (Select [Allocation_unit_id] from sys.allocation_units allocunits
127 INNER JOIN sys.partitions partitions ON (allocunits.type IN (1, 3)
128 AND partitions.hobt_id = allocunits.container_id) OR (allocunits.type = 2 
129 AND partitions.partition_id = allocunits.container_id)  
130 Where object_id=object_ID('' + @SchemaName_n_TableName + ''))
131 AND Operation in ('LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS') AND [Context] IN ('LCX_HEAP','LCX_CLUSTERED')
132 /*Use this subquery to filter the date*/
133  
134 AND [TRANSACTION ID] IN (Select DISTINCT [TRANSACTION ID] FROM sys.fn_dblog(NULL, NULL) 
135 Where Context IN ('LCX_NULL') AND Operation IN ('LOP_BEGIN_XACT')  
136 AND [Transaction Name]='UPDATE'
137 AND  CONVERT(NVARCHAR(11),[Begin Time]) BETWEEN @Date_From AND @Date_To)
138  
139 /****************************************/
140 GROUP BY B.[PAGE ID],A.[Slot ID],A.[AllocUnitId]--,[Transaction ID]
141 ORDER BY [Slot ID]
142  
143  
144 -- Convert the hex value data in string, convert it into Hex value as well. 
145 UPDATE @ModifiedRawData  SET [RowLog Contents 0] = cast('' AS XML).value('xs:hexBinary(substring(sql:column("[RowLog Contents 0_var]"), 0) )', 'varbinary(max)')
146 FROM @ModifiedRawData
147  
148 ---Now we have modifed data plus its slot ID , page ID and allocunit as well.
149 --After that we need to get the old values before modfication, these datas are in chunks.
150 DECLARE Page_Data_Cursor CURSOR FOR
151  
152 Select [PAGE ID],[Slot ID],[AllocUnitId],[Transaction ID],[RowLog Contents 0], [RowLog Contents 1],[RowLog Contents 3],[RowLog Contents 4]
153 ,Substring ([Log Record],[Log Record Fixed Length],([Log Record Length]+1)-([Log Record Fixed Length])) as [Log Record]
154 ,Operation
155 FROM    sys.fn_dblog(NULL, NULL)  
156 WHERE   AllocUnitId IN
157 (Select [Allocation_unit_id] from sys.allocation_units allocunits
158 INNER JOIN sys.partitions partitions ON (allocunits.type IN (1, 3)  
159 AND partitions.hobt_id = allocunits.container_id) OR (allocunits.type = 2 
160 AND partitions.partition_id = allocunits.container_id)  
161 Where object_id=object_ID('' + @SchemaName_n_TableName + ''))
162 AND Operation in ('LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS') And [Context] IN ('LCX_HEAP','LCX_CLUSTERED')
163 /*Use this sub query to filter the date*/
164  
165 AND [TRANSACTION ID] IN (Select DISTINCT [TRANSACTION ID] FROM    sys.fn_dblog(NULL, NULL) 
166 WHERE Context IN ('LCX_NULL') AND Operation IN ('LOP_BEGIN_XACT')  
167 AND [Transaction Name]='UPDATE'
168 AND  CONVERT(NVARCHAR(11),[Begin Time]) BETWEEN @Date_From AND @Date_To)
169  
170 /****************************************/
171 Order By [Slot ID],[Transaction ID] DESC
172  
173 OPEN Page_Data_Cursor
174  
175     FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID, @Slotid,@AllocUnitID,@TransactionID,@RowLogContents0,@RowLogContents1,@RowLogContents3,@RowLogContents4,@LogRecord,@Operation
176     WHILE @@FETCH_STATUS = 0
177     BEGIN
178            IF @Operation ='LOP_MODIFY_ROW' 
179               BEGIN
180                       /* If it is @Operation Type is 'LOP_MODIFY_ROW' then it is very simple to recover the modified data. The old data is in [RowLog Contents 0] Field and modified data is in [RowLog Contents 1] Field. Simply replace it with the modified data and get the old data.
181                       */
182                       INSERT INTO @ModifiedRawData ([PAGE ID],[Slot ID],[AllocUnitId],[RowLog Contents 0_var]) 
183                       SELECT TOP 1  @ConsolidatedPageID AS [PAGE ID],@Slotid AS [Slot ID],@AllocUnitID AS [AllocUnitId]
184                       ,REPLACE (UPPER([RowLog Contents 0_var]),UPPER(CAST('' AS XML).value('xs:hexBinary(sql:variable("@RowLogContents1") )', 'varchar(max)')),UPPER(cast('' AS XML).value('xs:hexBinary(sql:variable("@RowLogContents0") )', 'varchar(max)'))) AS [RowLog Contents 0_var]
185                       FROM  @ModifiedRawData WHERE [PAGE ID]=@ConsolidatedPageID And [Slot ID]=@Slotid And [AllocUnitId]=@AllocUnitID ORDER BY [ID] DESC
186  
187                       --- Convert the old data which is in string format to hex format.
188                       UPDATE @ModifiedRawData  SET [RowLog Contents 0] = cast('' AS XML).value('xs:hexBinary(substring(sql:column("[RowLog Contents 0_var]"), 0) )', 'varbinary(max)')
189                       FROM @ModifiedRawData Where [Slot ID]=@SlotID
190  
191               END
192          IF @Operation ='LOP_MODIFY_COLUMNS'                   
193              BEGIN
194  
195                           /* If it is @Operation Type is 'LOP_MODIFY_ROW' then we need to follow a different procedure to recover modified
196                              .Because this time the data is also in chunks but merge with the data log.
197                           */
198                             --First, we need to get the [RowLog Contents 3] Because in [Log Record] field the modified data is available after the [RowLog Contents 3] data.
199                             SET @RowLogContents3_Var=cast('' AS XML).value('xs:hexBinary(sql:variable("@RowLogContents3") )', 'varchar(max)')
200                             SET @LogRecord_Var =cast('' AS XML).value('xs:hexBinary(sql:variable("@LogRecord"))', 'varchar(max)')
201             
202                             DECLARE @RowLogData_Var VARCHAR(Max)
203                             DECLARE @RowLogData_Hex VARBINARY(Max)
204                             ---First get the modifed data chunks in string format 
205                             SET @RowLogData_Var = SUBSTRING(@LogRecord_Var, CHARINDEX(@RowLogContents3_Var,@LogRecord_Var) +LEN(@RowLogContents3_Var) ,LEN(@LogRecord_Var))
206                             --Then convert it into the hex values.
207                             SELECT @RowLogData_Hex=CAST('' AS XML).value('xs:hexBinary( substring(sql:variable("@RowLogData_Var"),0) )', 'varbinary(max)')
208                             FROM (SELECT CASE SUBSTRING(@RowLogData_Var, 1, 2) WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)
209                             DECLARE @TotalFixedLengthData INT 
210                             DECLARE @FixedLength_Offset INT
211                             DECLARE @VariableLength_Offset INT
212                             DECLARE @VariableLength_Offset_Start INT
213                             DECLARE @VariableLengthIncrease INT
214                             DECLARE @FixedLengthIncrease INT
215                             DECLARE @OldFixedLengthStartPosition INT
216                             DECLARE @FixedLength_Loc INT
217                             DECLARE @VariableLength_Loc INT
218                             DECLARE @FixedOldValues VARBINARY(MAX)
219                             DECLARE @FixedNewValues VARBINARY(MAX)
220                             DECLARE @VariableOldValues VARBINARY(MAX)
221                             DECLARE @VariableNewValues VARBINARY(MAX)
222                          
223                             -- Before recovering the modfied data we need to get the total fixed length data size and start position of the varaible data
224                              
225                             SELECT TOP 1 @TotalFixedLengthData=CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0] , 2 + 1, 2)))) 
226                             ,@VariableLength_Offset_Start=CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0] , 2 + 1, 2))))+5+CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0] , CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0] , 2 + 1, 2)))) + 1, 2))))/8.0))
227                             FROM @ModifiedRawData
228                             ORDER BY [ID] DESC
229  
230                             SET @FixedLength_Offset= CONVERT(BINARY(2),REVERSE(CONVERT(BINARY(4),(@RowLogContents0))))--)
231                             SET @VariableLength_Offset=CONVERT(int,CONVERT(BINARY(2),REVERSE(@RowLogContents0)))
232                              
233                             /* We already have modified data chunks in @RowLogData_Hex but this data is in merge format (modified plus actual data)
234                               So , here we need [Row Log Contents 1] field , because in this field we have the data length both the modified and actual data
235                                so this length will help us to break it into original and modified data chunks.
236                             */
237                             SET @FixedLength_Loc= CONVERT(INT,SUBSTRING(@RowLogContents1,1,1))
238                             SET @VariableLength_Loc =CONVERT(INT,SUBSTRING(@RowLogContents1,3,1))
239  
240                             /*First , we need to break Fix length data actual with the help of data length  */
241                             SET @OldFixedLengthStartPosition= CHARINDEX(@RowLogContents4,@RowLogData_Hex)
242                             SET @FixedOldValues = SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition,@FixedLength_Loc)
243                             SET @FixedLengthIncrease = (CASE WHEN (Len(@FixedOldValues)%4)=0 THEN 1 ELSE (4-(LEN(@FixedOldValues)%4))  END)
244                             /*After that , we need to break Fix length data modified data with the help of data length  */
245                             SET @FixedNewValues =SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition+@FixedLength_Loc+@FixedLengthIncrease,@FixedLength_Loc) 
246  
247                             /*Same we need to break the variable data with the help of data length*/
248                             SET @VariableOldValues =SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition+@FixedLength_Loc+@FixedLengthIncrease+@FixedLength_Loc+(@FixedLengthIncrease),@VariableLength_Loc)
249                             SET @VariableLengthIncrease =  (CASE WHEN (LEN(@VariableOldValues)%4)=0 THEN 1 ELSE (4-(Len(@VariableOldValues)%4))+1  END) 
250                             SET @VariableOldValues =(Case When @VariableLength_Loc =1 Then  @VariableOldValues+0x00 else @VariableOldValues end)
251                  
252                             SET @VariableNewValues =SUBSTRING(SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition+@FixedLength_Loc+@FixedLengthIncrease+@FixedLength_Loc+(@FixedLengthIncrease-1)+@VariableLength_Loc+@VariableLengthIncrease,Len(@RowLogData_Hex)+1),1,Len(@RowLogData_Hex)+1) --LEN(@VariableOldValues)
253                             
254                             /*here we need to replace the fixed length &  variable length actaul data with modifed data 
255                             */
256                              
257                             Select top 1 @VariableNewValues=Case
258                             When Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)+1),[RowLog Contents 0])<>0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)+1)
259                             When Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)),[RowLog Contents 0])<>0 Then  Substring(@VariableNewValues,0,Len(@VariableNewValues))
260                             When Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-1),[RowLog Contents 0])<>0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-1)--3 --Substring(@VariableNewValues,0,Len(@VariableNewValues)-1)
261                             When Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-2),[RowLog Contents 0])<>0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-2)
262                             When Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-3),[RowLog Contents 0])<>0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-3) --5--Substring(@VariableNewValues,0,Len(@VariableNewValues)-3)
263                             End
264                             FROM @ModifiedRawData  Where [Slot ID]=@SlotID  ORDER BY [ID] DESC
265                                           
266                             INSERT INTO @ModifiedRawData ([PAGE ID],[Slot ID],[AllocUnitId],[RowLog Contents 0_var],[RowLog Contents 0]) 
267                             SELECT TOP 1  @ConsolidatedPageID AS [PAGE ID],@Slotid AS [Slot ID],@AllocUnitID AS [AllocUnitId],NULL
268                             ,CAST(REPLACE(SUBSTRING([RowLog Contents 0],0,@TotalFixedLengthData+1),@FixedNewValues, @FixedOldValues) AS VARBINARY(max))
269                             + SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 1, 2)
270                             + SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 3, CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 1, 2))))/8.0)))
271                             + SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 3 + CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 1, 2))))/8.0)), 2)
272                             + Substring([RowLog Contents 0],@VariableLength_Offset_Start,(@VariableLength_Offset-(@VariableLength_Offset_Start-1)))
273                             + CAST(REPLACE(SUBSTRING([RowLog Contents 0],@VariableLength_Offset+1,Len(@VariableNewValues))
274                             , @VariableNewValues
275                             , @VariableOldValues) AS VARBINARY) 
276                             + Substring([RowLog Contents 0],@VariableLength_Offset+Len(@VariableNewValues)+1,LEN([RowLog Contents 0]))
277                             FROM @ModifiedRawData  Where [Slot ID]=@SlotID  ORDER BY [ID] DESC
278                             
279              END
280  
281         FETCH NEXT FROM Page_Data_Cursor INTO   @ConsolidatedPageID, @Slotid,@AllocUnitID,@TransactionID,@RowLogContents0,@RowLogContents1,@RowLogContents3,@RowLogContents4,@LogRecord,@Operation
282     END
283  
284 CLOSE Page_Data_Cursor
285 DEALLOCATE Page_Data_Cursor
286  
287 DECLARE @RowLogContents VARBINARY(8000)
288 Declare @AllocUnitName NVARCHAR(Max)
289 Declare @SQL NVARCHAR(Max)
290  
291 DECLARE @bitTable TABLE
292 (
293   [ID] INT,
294   [Bitvalue] INT
295 )
296 ----Create table to set the bit position of one byte.
297  
298 INSERT INTO @bitTable
299 SELECT 0,2 UNION ALL
300 SELECT 1,2 UNION ALL
301 SELECT 2,4 UNION ALL
302 SELECT 3,8 UNION ALL
303 SELECT 4,16 UNION ALL
304 SELECT 5,32 UNION ALL
305 SELECT 6,64 UNION ALL
306 SELECT 7,128
307  
308 --Create table to collect the row data.
309 DECLARE @DeletedRecords TABLE
310 (
311     [ID] INT IDENTITY(1,1),
312     [RowLogContents]    VARBINARY(8000),
313     [AllocUnitID]       BIGINT,
314     [Transaction ID]    NVARCHAR(Max),
315     [Slot ID]           INT,
316     [FixedLengthData]   SMALLINT,
317     [TotalNoOfCols]     SMALLINT,
318     [NullBitMapLength]  SMALLINT,
319     [NullBytes]         VARBINARY(8000),
320     [TotalNoofVarCols]  SMALLINT,
321     [ColumnOffsetArray] VARBINARY(8000),
322     [VarColumnStart]    SMALLINT,
323     [NullBitMap]        VARCHAR(MAX)
324 )
325 --Create a common table expression to get all the row data plus how many bytes we have for each row.
326 ;WITH RowData AS (
327 SELECT
328  
329 [RowLog Contents 0] AS [RowLogContents] 
330  
331 ,@AllocUnitID AS [AllocUnitID] 
332  
333 ,[ID] AS [Transaction ID]  
334  
335 ,[Slot ID] as [Slot ID]
336 --[Fixed Length Data] = Substring (RowLog content 0, Status Bit A+ Status Bit B + 1,2 bytes)
337 ,CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) AS [FixedLengthData]  --@FixedLengthData
338  
339  --[TotalnoOfCols] =  Substring (RowLog content 0, [Fixed Length Data] + 1,2 bytes)
340 ,CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
341 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2)))) as  [TotalNoOfCols]
342  
343 --[NullBitMapLength]=ceiling([Total No of Columns] /8.0)
344 ,CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
345 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2))))/8.0)) as [NullBitMapLength] 
346  
347 --[Null Bytes] = Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [NullBitMapLength] )
348 ,SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3,
349 CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
350 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2))))/8.0))) as [NullBytes]
351  
352 --[TotalNoofVarCols] = Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 )
353 ,(CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70) THEN
354 CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
355 CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3
356 + CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
357 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2))))/8.0)), 2))))  ELSE null  END) AS [TotalNoofVarCols] 
358  
359 --[ColumnOffsetArray]= Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 , [TotalNoofVarCols]*2 )
360 ,(CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70) THEN
361 SUBSTRING([RowLog Contents 0]
362 , CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3
363 + CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
364 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2))))/8.0)) + 2
365 , (CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70) THEN
366 CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
367 CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3
368 + CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
369 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2))))/8.0)), 2))))  ELSE null  END)
370 * 2)  ELSE null  END) AS [ColumnOffsetArray] 
371  
372 --  Variable column Start = Status Bit A+ Status Bit B + [Fixed Length Data] + [Null Bitmap length] + 2+([TotalNoofVarCols]*2)
373 ,CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1)In (0x30,0x70)
374 THEN  (
375 CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 4 
376  
377 + CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
378 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2))))/8.0)) 
379  
380 + ((CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70) THEN
381 CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
382 CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3
383 + CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2)
384 ,REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 1, 2))))/8.0)), 2))))  ELSE null  END) * 2)) 
385  
386 ELSE null End AS [VarColumnStart]
387 From @ModifiedRawData
388  
389 ),
390  
391 ---Use this technique to repeate the row till the no of bytes of the row.
392 N1 (n) AS (SELECT 1 UNION ALL SELECT 1),
393 N2 (n) AS (SELECT 1 FROM N1 AS X, N1 AS Y),
394 N3 (n) AS (SELECT 1 FROM N2 AS X, N2 AS Y),
395 N4 (n) AS (SELECT ROW_NUMBER() OVER(ORDER BY X.n)
396            FROM N3 AS X, N3 AS Y)
397  
398 insert into @DeletedRecords
399 Select   RowLogContents
400         ,[AllocUnitID]
401         ,[Transaction ID]
402         ,[Slot ID]
403         ,[FixedLengthData]
404         ,[TotalNoOfCols]
405         ,[NullBitMapLength]
406         ,[NullBytes]
407         ,[TotalNoofVarCols]
408         ,[ColumnOffsetArray]
409         ,[VarColumnStart]
410          --Get the Null value against each column (1 means null zero means not null)
411         ,[NullBitMap]=(REPLACE(STUFF((SELECT ',' +
412         (CASE WHEN [ID]=0 THEN CONVERT(NVARCHAR(1),(SUBSTRING(NullBytes, n, 1) % 2))  ELSE CONVERT(NVARCHAR(1),((SUBSTRING(NullBytes, n, 1) / [Bitvalue]) % 2)) END) --as [nullBitMap]
413 FROM
414 N4 AS Nums
415 Join RowData AS C ON n<=NullBitMapLength
416 Cross Join @bitTable WHERE C.[RowLogContents]=D.[RowLogContents] ORDER BY [RowLogContents],n ASC FOR XML PATH('')),1,1,''),',',''))
417 FROM RowData D
418  
419 CREATE TABLE [#temp_Data]
420 (
421    
422     [FieldName]  VARCHAR(MAX) COLLATE database_default NOT NULL,
423     [FieldValue] VARCHAR(MAX) COLLATE database_default NULL,
424     [Rowlogcontents] VARBINARY(8000),
425     [Transaction ID] VARCHAR(MAX) COLLATE database_default NOT NULL,
426     [Slot ID] INT,
427     [NonID] INT,
428     --[System_type_id] int
429  
430 )
431 ---Create common table expression and join it with the rowdata table
432 --to get each column details
433 ;With CTE AS (
434 /*This part is for variable data columns*/
435 SELECT
436 A.[ID],
437 Rowlogcontents,
438 [Transaction ID],
439 [Slot ID],
440 NAME ,
441 cols.leaf_null_bit AS nullbit,
442 leaf_offset,
443 ISNULL(syscolumns.length, cols.max_length) AS [length],
444 cols.system_type_id,
445 cols.leaf_bit_position AS bitpos,
446 ISNULL(syscolumns.xprec, cols.precision) AS xprec,
447 ISNULL(syscolumns.xscale, cols.scale) AS xscale,
448 SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null,
449 --Calculate the variable column size from the variable column offset array
450 (CASE WHEN leaf_offset<1 and SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=0 THEN
451 CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * leaf_offset*-1) - 1, 2)))) ELSE 0 END) AS [Column value Size],
452  
453 ---Calculate the column length
454 (CASE WHEN leaf_offset<1 and SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=0 THEN  CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * (leaf_offset*-1)) - 1, 2))))
455 - ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * ((leaf_offset*-1) - 1)) - 1, 2)))), 0), [varColumnStart])
456 ELSE 0 END) AS [Column Length]
457  
458 --Get the Hexa decimal value from the RowlogContent
459 --HexValue of the variable column=Substring([Column value Size] - [Column Length] + 1,[Column Length])
460 --This is the data of your column but in the Hexvalue
461 ,CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=1 THEN NULL ELSE
462 SUBSTRING(Rowlogcontents,((CASE WHEN leaf_offset<1 and SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=0 THEN CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * leaf_offset*-1) - 1, 2)))) ELSE 0 END)
463 - ((CASE WHEN leaf_offset<1 and SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=0 THEN  CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * (leaf_offset*-1)) - 1, 2))))
464 - ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * ((leaf_offset*-1) - 1)) - 1, 2)))), 0), [varColumnStart])
465 ELSE 0 END))) + 1,((CASE WHEN leaf_offset<1 and SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=0 THEN  CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * (leaf_offset*-1)) - 1, 2))))
466 - ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING ([ColumnOffsetArray], (2 * ((leaf_offset*-1) - 1)) - 1, 2)))), 0), [varColumnStart])
467 ELSE 0 END))) END AS hex_Value
468  
469 FROM @DeletedRecords A
470 Inner Join sys.allocation_units allocunits On A.[AllocUnitId]=allocunits.[Allocation_Unit_Id]
471 INNER JOIN sys.partitions partitions ON (allocunits.type IN (1, 3)
472 AND partitions.hobt_id = allocunits.container_id) OR (allocunits.type = 2 AND partitions.partition_id = allocunits.container_id)
473 INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
474 LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id AND syscolumns.colid = cols.partition_column_id
475 WHERE leaf_offset<0
476  
477 UNION
478 /*This part is for fixed data columns*/
479 SELECT 
480 A.[ID],
481 Rowlogcontents,
482 [Transaction ID],
483 [Slot ID],
484 NAME ,
485 cols.leaf_null_bit AS nullbit,
486 leaf_offset,
487 ISNULL(syscolumns.length, cols.max_length) AS [length],
488 cols.system_type_id,
489 cols.leaf_bit_position AS bitpos,
490 ISNULL(syscolumns.xprec, cols.precision) AS xprec,
491 ISNULL(syscolumns.xscale, cols.scale) AS xscale,
492 SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null,
493 (SELECT TOP 1 ISNULL(SUM(CASE WHEN C.leaf_offset >1 THEN max_length ELSE 0 END),0) FROM
494 sys.system_internals_partition_columns C WHERE cols.partition_id =C.partition_id And C.leaf_null_bit<cols.leaf_null_bit)+5 AS [Column value Size],
495 syscolumns.length AS [Column Length]
496  
497 ,CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=1 THEN NULL ELSE
498 SUBSTRING
499 (
500 Rowlogcontents,(SELECT TOP 1 ISNULL(SUM(CASE WHEN C.leaf_offset >1 THEN max_length ELSE 0 END),0) FROM
501 sys.system_internals_partition_columns C where cols.partition_id =C.partition_id And C.leaf_null_bit<cols.leaf_null_bit)+5
502 ,syscolumns.length) END AS hex_Value
503 FROM @DeletedRecords A
504 Inner Join sys.allocation_units allocunits ON A.[AllocUnitId]=allocunits.[Allocation_Unit_Id]
505 INNER JOIN sys.partitions partitions ON (allocunits.type IN (1, 3)
506  AND partitions.hobt_id = allocunits.container_id) OR (allocunits.type = 2 AND partitions.partition_id = allocunits.container_id)
507 INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
508 LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id AND syscolumns.colid = cols.partition_column_id
509 WHERE leaf_offset>0 )
510  
511 --Converting data from Hexvalue to its orgional datatype.
512 --Implemented datatype conversion mechanism for each datatype
513 --Select * from sys.columns Where [object_id]=object_id('' + @SchemaName_n_TableName + '')
514 --Select * from CTE
515  
516 INSERT INTO #temp_Data
517 SELECT
518 NAME,
519 CASE
520  WHEN system_type_id IN (231, 239) THEN  LTRIM(RTRIM(CONVERT(NVARCHAR(max),hex_Value)))  --NVARCHAR ,NCHAR
521  WHEN system_type_id IN (167,175) THEN  LTRIM(RTRIM(CONVERT(VARCHAR(max),REPLACE(hex_Value, 0x00, 0x20))))  --VARCHAR,CHAR
522  WHEN system_type_id = 48 THEN CONVERT(VARCHAR(MAX), CONVERT(TINYINT, CONVERT(BINARY(1), REVERSE (hex_Value)))) --TINY INTEGER
523  WHEN system_type_id = 52 THEN CONVERT(VARCHAR(MAX), CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (hex_Value)))) --SMALL INTEGER
524  WHEN system_type_id = 56 THEN CONVERT(VARCHAR(MAX), CONVERT(INT, CONVERT(BINARY(4), REVERSE(hex_Value)))) -- INTEGER
525  WHEN system_type_id = 127 THEN CONVERT(VARCHAR(MAX), CONVERT(BIGINT, CONVERT(BINARY(8), REVERSE(hex_Value))))-- BIG INTEGER
526  WHEN system_type_id = 61 Then CONVERT(VARCHAR(MAX),CONVERT(DATETIME,CONVERT(VARBINARY(8000),REVERSE (hex_Value))),100) --DATETIME
527  --WHEN system_type_id IN( 40) Then CONVERT(VARCHAR(MAX),CONVERT(DATE,CONVERT(VARBINARY(8000),(hex_Value))),100) --DATE This datatype only works for SQL Server 2008
528  WHEN system_type_id =58 Then CONVERT(VARCHAR(MAX),CONVERT(SMALLDATETIME,CONVERT(VARBINARY(8000),REVERSE(hex_Value))),100) --SMALL DATETIME
529  WHEN system_type_id = 108 THEN CONVERT(VARCHAR(MAX), CAST(CONVERT(NUMERIC(38,30), CONVERT(VARBINARY,CONVERT(VARBINARY,xprec)+CONVERT(VARBINARY,xscale))+CONVERT(VARBINARY(1),0) + hex_Value) as FLOAT)) --- NUMERIC
530  WHEN system_type_id In(60,122) THEN CONVERT(VARCHAR(MAX),Convert(MONEY,Convert(VARBINARY(8000),Reverse(hex_Value))),2) --MONEY,SMALLMONEY
531  WHEN system_type_id =106 THEN CONVERT(VARCHAR(MAX), CAST(CONVERT(Decimal(38,34), CONVERT(VARBINARY,Convert(VARBINARY,xprec)+CONVERT(VARBINARY,xscale))+CONVERT(VARBINARY(1),0) + hex_Value) as FLOAT)) --- DECIMAL
532  WHEN system_type_id = 104 THEN CONVERT(VARCHAR(MAX),CONVERT (BIT,CONVERT(BINARY(1), hex_Value)%2))  -- BIT
533  WHEN system_type_id =62 THEN  RTRIM(LTRIM(STR(CONVERT(FLOAT,SIGN(CAST(CONVERT(VARBINARY(8000),Reverse(hex_Value)) AS BIGINT)) * (1.0 + (CAST(CONVERT(VARBINARY(8000),Reverse(hex_Value)) AS BIGINT) & 0x000FFFFFFFFFFFFF) * POWER(CAST(2 AS FLOAT), -52)) * POWER(CAST(2 AS FLOAT),((CAST(CONVERT(VARBINARY(8000),Reverse(hex_Value)) AS BIGINT) & 0x7ff0000000000000) / EXP(52 * LOG(2))-1023))),53,LEN(hex_Value)))) --- FLOAT
534  When system_type_id =59 THEN  Left(LTRIM(STR(CAST(SIGN(CAST(Convert(VARBINARY(8000),REVERSE(hex_Value)) AS BIGINT))* (1.0 + (CAST(CONVERT(VARBINARY(8000),Reverse(hex_Value)) AS BIGINT) & 0x007FFFFF) * POWER(CAST(2 AS Real), -23)) * POWER(CAST(2 AS Real),(((CAST(CONVERT(VARBINARY(8000),Reverse(hex_Value)) AS INT) )& 0x7f800000)/ EXP(23 * LOG(2))-127))AS REAL),23,23)),8) --Real
535  WHEN system_type_id In (165,173) THEN (CASE WHEN CHARINDEX(0x,cast('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))', 'VARBINARY(8000)')) = 0 THEN '0x' ELSE '' END) +cast('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))', 'varchar(max)') -- BINARY,VARBINARY
536  WHEN system_type_id =36 THEN CONVERT(VARCHAR(MAX),CONVERT(UNIQUEIDENTIFIER,hex_Value)) --UNIQUEIDENTIFIER
537  END AS FieldValue
538 ,[Rowlogcontents]
539 ,[Transaction ID]
540 ,[Slot ID]
541 ,[ID]
542 FROM CTE ORDER BY nullbit
543  
544 /*Create Update statement*/
545 /*Now we have the modified and actual data as well*/
546 /*We need to create the update statement in case of recovery*/
547  
548 ;With CTE AS (SELECT
549 (CASE
550 WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL(+ '''' + [A].[FieldValue]+ '''','NULL')+ ' ,'+' '
551 WHEN system_type_id In (231,239) THEN  QUOTENAME([Name]) + '='  + ISNULL(+ 'N''' +[A].[FieldValue]+ '''','NULL')+ ' ,'+''
552 WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '='  + ISNULL(+  ''''+[A].[FieldValue]+ '''','NULL') + '  ,'+' '
553 WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '='  + ISNULL([A].[FieldValue],'NULL')+ ' ,'+' '
554 END) as [Field]
555 ,A.[Slot ID]
556 ,A.[Transaction ID] as [Transaction ID]
557 ,'D' AS [Type] 
558 ,[A].Rowlogcontents
559 ,[A].[NonID]
560 FROM #temp_Data AS [A]
561 INNER JOIN #temp_Data AS [B] ON [A].[FieldName]=[B].[FieldName]
562 AND [A].[Slot ID]=[B].[Slot ID]
563 --And [A].[Transaction ID]=[B].[Transaction ID]+1
564 AND [B].[Transaction ID]=  (SELECT Min(Cast([Transaction ID] as int)) as [Transaction ID]  FROM #temp_Data AS [C]
565 WHERE [A].[Slot ID]=[C].[Slot ID]
566 GROUP BY [Slot ID])
567 INNER JOIN sys.columns [D] On  [object_id]=object_id('' + @SchemaName_n_TableName + '')
568 AND A.[Fieldname] = D.[name]
569 WHERE ISNULL([A].[FieldValue],'')<>ISNULL([B].[FieldValue],'')
570 UNION ALL
571  
572 SELECT(CASE
573 WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL(+ '''' + [A].[FieldValue]+ '''','NULL')+ ' AND '+''
574 WHEN system_type_id In (231,239) THEN  QUOTENAME([Name]) + '='+ ISNULL(+ 'N''' +[A].[FieldValue]+ '''','NULL')+ ' AND '+''
575 WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '=' + ISNULL(+  ''''+[A].[FieldValue]+ '''','NULL') + ' AND '+''
576 WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '='  + ISNULL([A].[FieldValue],'NULL') + ' AND '+''
577 END) AS [Field]
578 ,A.[Slot ID]
579 ,A.[Transaction ID] AS [Transaction ID]
580 ,'S' AS [Type]
581 ,[A].Rowlogcontents
582 ,[A].[NonID]
583 FROM #temp_Data AS [A]
584 INNER JOIN #temp_Data AS [B] ON [A].[FieldName]=[B].[FieldName]
585 AND [A].[Slot ID]=[B].[Slot ID]
586 --And [A].[Transaction ID]=[B].[Transaction ID]+1
587 AND [B].[Transaction ID]=  (SELECT Min(Cast([Transaction ID] as int)) as [Transaction ID] FROM #temp_Data AS [C]
588 WHERE [A].[Slot ID]=[C].[Slot ID]
589 GROUP BY [Slot ID])
590 INNER JOIN sys.columns [D] ON  [object_id]=object_id('' + @SchemaName_n_TableName + '')
591 AND [A].[Fieldname]=D.[name]
592 WHERE ISNULL([A].[FieldValue],'')=ISNULL([B].[FieldValue],'')
593 AND A.[Transaction ID] NOT IN (SELECT Min(Cast([Transaction ID] as int)) as [Transaction ID] FROM #temp_Data AS [C]
594 WHERE [A].[Slot ID]=[C].[Slot ID]
595 GROUP BY [Slot ID])
596 )
597  
598 ,CTEUpdateQuery AS (SELECT 'UPDATE ' +  @SchemaName_n_TableName +  ' SET ' + LEFT(
599 STUFF((SELECT ' ' + ISNULL([Field],'')+ ' ' FROM CTE B 
600 WHERE A.[Slot ID]=B.[Slot ID] AND A.[Transaction ID]=B.[Transaction ID] And B.[Type]='D' FOR XML PATH('') ),1,1,''), 
601  
602 LEN(STUFF((SELECT ' ' +ISNULL([Field],'')+ ' ' FROM CTE B 
603 WHERE A.[Slot ID]=B.[Slot ID] AND A.[Transaction ID]=B.[Transaction ID] And B.[Type]='D' FOR XML PATH('') ),1,1,'') )-2)
604  
605 + '  WHERE  ' +
606  
607 LEFT(STUFF((SELECT ' ' +ISNULL([Field],'')+ ' ' FROM CTE C 
608 WHERE A.[Slot ID]=C.[Slot ID] AND A.[Transaction ID]=C.[Transaction ID] And C.[Type]='S' FOR XML PATH('') ),1,1,'') ,
609  
610 LEN(STUFF((SELECT ' ' +ISNULL([Field],'')+ ' ' FROM CTE C 
611 WHERE A.[Slot ID]=C.[Slot ID] AND A.[Transaction ID]=C.[Transaction ID] And C.[Type]='S' FOR XML PATH('') ),1,1,''))-4)
612 AS [Update Statement],
613 [Slot ID]
614 ,[Transaction ID]
615 ,Rowlogcontents
616 ,[A].[NonID]
617 FROM CTE A
618 GROUP BY [Slot ID]
619 ,[Transaction ID]
620 ,Rowlogcontents
621 ,[A].[NonID] )
622  
623 INSERT INTO #temp_Data 
624 SELECT 'Update Statement',ISNULL([Update Statement],''),[Rowlogcontents],[Transaction ID],[Slot ID],[NonID] FROM CTEUpdateQuery
625  
626 /**************************/
627 --Create the column name in the same order to do pivot table.
628 DECLARE @FieldName VARCHAR(max)
629 SET @FieldName = STUFF(
630 (
631 SELECT ',' + CAST(QUOTENAME([Name]) AS VARCHAR(MAX)) FROM syscolumns WHERE id=object_id('' + @SchemaName_n_TableName + '')
632  
633 FOR XML PATH('')
634 ), 1, 1, '')
635  
636 --Finally did pivot table and got the data back in the same format.
637 --The [Update Statement] column will give you the query that you can execute in case of recovery.
638 SET @sql = 'SELECT ' + @FieldName  + ',[Update Statement] FROM #temp_Data 
639 PIVOT (Min([FieldValue]) FOR FieldName IN (' + @FieldName  + ',[Update Statement])) AS pvt
640 Where [Transaction ID] NOT In (Select Min(Cast([Transaction ID] as int)) as [Transaction ID] from #temp_Data
641 Group By [Slot ID]) ORDER BY Convert(int,[Slot ID]),Convert(int,[Transaction ID])'
642 Print @sql
643 EXEC sp_executesql @sql
644  
645 GO
646 --Execute the procedure like
647 --Recover_Modified_Data_Proc 'Database name''Schema.table name','Date from' ,'Date to'
648  
649 ----EXAMPLE #1 : FOR ALL MODIFIED RECORDS
650 EXEC Recover_Modified_Data_Proc 'test','dbo.Student'
651 GO
652 --EXAMPLE #2 : FOR ANY SPECIFIC DATE RANGE
653 EXEC Recover_Modified_Data_Proc  'test','dbo.Student','2000/01/01','9999/12/31'
654 --It will give you the result of all modified records.
View Code
 
原文地址:https://www.cnblogs.com/xiaozizi/p/5953449.html