Tag Archives: script

AWS – Redshift – Lock e Block

Por incrível que pareça o Redshift sofre com problemas de Lock e Block da mesma forma que um banco transacional qualquer.

Como qualquer post sobre nuvem, até o momento dessa publicação, o Redshift não tem uma interface que monitora Lock e Block, ela monitora conexões ativas, querys em execução mas não lock e block.

A query para monitorar o Redshift é a seguinte:

select a.txn_owner, a.txn_db, a.xid, a.pid, a.txn_start, a.lock_mode, a.relation as table_id,nvl(trim(c."name"),d.relname) as tablename, a.granted,b.pid as blocking_pid ,datediff(s,a.txn_start,getdate())/86400||' days '||datediff(s,a.txn_start,getdate())%86400/3600||' hrs '||datediff(s,a.txn_start,getdate())%3600/60||' mins '||datediff(s,a.txn_start,getdate())%60||' secs' as txn_duration
from svv_transactions a 
left join (select pid,relation,granted from pg_locks group by 1,2,3) b 
on a.relation=b.relation and a.granted='f' and b.granted='t' 
left join (select * from stv_tbl_perm where slice=0) c 
on a.relation=c.id 
left join pg_class d on a.relation=d.oid
where  a.relation is not null;

e para dar um kill no processo

select pg_terminate_backend(PID);

o resultado deve vir como “1”

No link abaixo tem mais informações:

https://aws.amazon.com/pt/premiumsupport/knowledge-center/prevent-locks-blocking-queries-redshift/

AWS – Redshift – Usuário para leituras

O Redshift tem umas vantagens bem interessantes, baixo custo, RDS, baixa necessidade de manutenção.

No fundo ele é um PostgreSQL modificado para prover volume de dados e não ficar trabalhando como OLTP, ele é ótimo como estrutura para DW.

Imagine o seguinte cenário, você vende uma solução mas precisa prover um acesso do seu cliente para que ele consiga acessar uma parte dos dados diretamente na sua estrutura de banco, para ele “ter a liberdade” de cruzar esses dados, montar estruturas de relatórios, etc. da forma que ele achar mais interessante, ou até mesmo exportar esses dados para uma estrutura dele e usar da forma que achar melhor.

Certo, temos várias formas de fazer isso, todas tem seus prós e contras, mas nesse caso vou usar como exemplo justamente o título do post, vamos colocar os dados no Redshift.

Então, você tem alguma forma de extração de dados incrementais (SSIS, Pentaho, Informática, estagiário, etc.) que leva os dados do seu OLTP para o Redshift e isso funciona bem.

Agora você precisa criar a estrutura de permissões para liberar o acesso do seu cliente para essa estrutura de dados.

Uma coisa muito importante : Todos os usuários do Redshift são exclusivos do banco de dados e não da instância, Então caso o cliente tenha mais de um banco ou você queira dar permissão para mais de um banco, siga o processo quantas vezes for necessário.

— Normalmente quando os objetos são criados no Redshift ele ficam armazenados no schema public.
— Isso não é um problema, o problema começa quando é criado um schema para armazenar um outro conjunto de objetos
— para um setor da empresa, ou um outro departamento…
— Quando isso acontece, o usuário owner da carga dos objetos tem acesso a essa estrutura de dados sem problema, mas novos usuários,
— ou usuários permissonalizados não tem a permissão para os objetos ou para novos objetos nesse schema.
— O script abaixo tente a sanar um cenário em que você quer liberar o acesso de select para os objetos e novo objetos em um schema pulic
— ou personalizado sem ter que ficar dando grant toda a vez que novos objetos são criados.
— Outra opção de uso é caso você tenha um Redshift na sua empresa e venda como serviço ele como datalake para algum cliente.
— dessa forma você consegue liberar um usuário para que o cliente acesse a estrutura de dados e consiga baixar os dados.

-- Normalmente quando os objetos são criados no Redshift ele ficam armazenados no schema public.
-- Isso não é um problema, o problema começa quando é criado um schema para armazenar um outro conjunto de objetos
-- para um setor da empresa, ou um outro departamento...
-- Quando isso acontece, o usuário owner da carga dos objetos tem acesso a essa estrutura de dados sem problema, mas novos usuários,
-- ou usuários permissonalizados não tem a permissão para os objetos ou para novos objetos nesse schema.
-- O script abaixo tente a sanar um cenário em que você quer liberar o acesso de select para os objetos e novo objetos em um schema pulic
-- ou personalizado sem ter que ficar dando grant toda a vez que novos objetos são criados.
-- Outra opção de uso é caso você tenha um Redshift na sua empresa e venda como serviço ele como datalake para algum cliente.
-- dessa forma você consegue liberar um usuário para que o cliente acesse a estrutura de dados e consiga baixar os dados.

-- criar um usuário
create user <username> with password ‘<password>’;

-- cria um grupo para receber as permissões
create group data_viewers;

-- adiciona o usuário ao grupo
alter group data_viewers add user <username>;

-- nesse caso remove a opção de criar objetos para os usuários do grupo
revoke create on schema public from group data_viewers;

-- atribui acesso no schema public ao grupo
grant usage on schema public to group data_viewers;

-- atribui select em todas as tabelas do schema public para o grupo
grant select on all tables in schema public to group data_viewers;

-- atribui acesso a futuros objetos do schema public para o grupo
alter default privileges in schema public grant select on tables to group data_viewers

novos códigos serão criados também em outro repositório:

https://github.com/bigleka

Telegram, Bot e PowerShell, uma máquina de alerta

Como DBA´s estamos sujeitos a jobs, alertas, sistemas, coisas do além, gerentes, desenvolvedores, intervenções místicas, etc. executando ações no banco de dados.

Não da pra ficar olhando de perto o ambiente a todo o momento analisando cada ação, monitorando tudo o que ocorre, se preparando para o pior.

Sendo um bom DBA, você deve ter um monte de Jobs, Alertas, Operadores, Sistemas de Monitoração, aquele estagiário sendo escalpelado, entre outras formas de monitorar seus bancos, a ideia aqui é trazer mais uma opção para atazanar sua vida monótona.

Vamos a receita do desastre,,, Para isso vamos precisar:

  1. Que você tenha uma conta no Telegram
  2. Instale o aplicativo do Telegram no seu Windows
  3. Crie um Bot
  4. Configure o Bot
  5. Crie um canal e adicione esse Bot como administrador a um canal
  6. Mande uma mensagem para esse canal para criar um ID
  7. Acesse uma URL do Telegram com a chave do BOT para pegar o ID do canal
  8. Crie uma função no Powershell
  9. Declare a variável com o nome do Bot e a chave
  10. Mande uma mensagem

Vamos começar,,,

Conta do Telegram

  • PQ está lendo essa parte? vai para a próxima,,, se não sabe fazer isso nem adianta continuar…

Instale o aplicativo do Telegram no seu Windows

  • Esse ponto é interessante, acesse o cliente web do Telegram e baixe a versão para seu SO, as configurações do Bot serão feitas através do cliente e não pelo celular ou Web

Crie um Bot

  • Com sua contra criada e app instalado, acesse essa URL https://t.me/BotFather . Ele é o Bot que cria os Bot´s
  • Para criar digite /start
  • Depois digite /newbot
  • De um nome para seu Bot
  • Agora crie um username para seu bot, ele tem que terminar com bot
  • Você vai receber uma mensagem de resposta com um textão e a parte que interessa que é o TOKEN. ANOTA ISSO!
  • Aqui vale uma dica, o Bot vai aparecer em pesquisa para qualquer pessoa que use o Telegram, mas só quem tem o token vai poder realmente usar ele.

Configure o Bot

  • Adicione uma descrição para ele, uma hora você ou alguém vai fazer manutenção e vai ter que lembrar para que ele serve
  • Usando /setdescription adicione uma descrição

Crie um canal e adicione esse Bot como administrador a um canal

  • Agora usando o cliente do SO, Web ou o app do celular, crie um canal no Telegram e deixe como privado.
  • Adicione um novo membro para esse canal, nesse caso o seu Bot, vai aparecer um alerta de que o Bot precisa ser administrador do canal e bla bla bla

Mande uma mensagem para esse canal para criar um ID

  • Só o fato de criar um canal não efetiva cria o canal, até esse momento ele não tem um ID
  • Mande uma mensagem qualquer para o canal para ele criar a estrutura com o ID

Acesse uma URL do Telegram com a chave do BOT para pegar o ID do canal

  • Após mandar a mensagem acesse a URL com o token do seu Bot
  • a URL é alguma coisa assim: https://api.telegram.org/bot<aquele token do seu Bot>/getUpdates
  • deixa escrito o nome bot, remove o < e o > e cola o token
  • deve abrir uma página com alguma coisa parecida com:
{"ok":true,"result":[{"update_id":129494597,
"message":{"message_id":3,"from":{"id":XXXXXX,"is_bot":false,"first_name":"Ricardo","last_name":"Leka"},"chat":{"id":XXXXXX,"first_name":"Ricardo","last_name":"Leka","type":"private"},"date":1614187816,"text":"/start","entities":[{"offset":0,"length":6,"type":"bot_command"}]}},{"update_id":129494598,
"message":{"message_id":4,"from":{"id":XXXXXX,"is_bot":false,"first_name":"Ricardo","last_name":"Leka","language_code":"pt-br"},"chat":{"id":XXXXXX,"first_name":"Ricardo","last_name":"Leka","type":"private"},"date":1614187832,"text":"/getchatid","entities":[{"offset":0,"length":10,"type":"bot_command"}]}},{"update_id":129494599,
"channel_post":{"message_id":2,"sender_chat":{"id":-XXXX,"title":"MC1_Notify","type":"channel"} ,"chat":{"id":-XXXX,"title":"Nome_Do_Canal","type":"channel"} ,"date":1614187878,"text":"teste"}}]}

O que interessa para nós é um desses últimos “id” que estão com um símbolo de menos na frente “id”:-XXXX

Crie uma função no Powershell

  • Agora a parte mágica, vamos usar um powershell para criar a função que vai conectar na API do Telegram e mandar a mensagem…
<#
.Synopsis
    Sends Telegram text message via Bot API
.DESCRIPTION
    Uses Telegram Bot API to send text message to specified Telegram chat. Several options can be specified to adjust message parameters.
.EXAMPLE
    $bot = "#########:xxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxx"
    $chat = "-#########"
    Send-TelegramTextMessage -BotToken $bot -ChatID $chat -Message "Hello"
.EXAMPLE
    $bot = "#########:xxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxx"
    $chat = "-#########"

    Send-TelegramTextMessage `
        -BotToken $bot `
        -ChatID $chat `
        -Message "Hello *chat* _channel_, check out this link: [TechThoughts](http://techthoughts.info/)" `
        -ParseMode Markdown `
        -Preview $false `
        -Notification $false `
        -Verbose
.PARAMETER BotToken
    Use this token to access the HTTP API
.PARAMETER ChatID
    Unique identifier for the target chat
.PARAMETER Message
    Text of the message to be sent
.PARAMETER ParseMode
    Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message. Default is Markdown.
.PARAMETER Preview
    Disables link previews for links in this message. Default is $false
.PARAMETER Notification
    Sends the message silently. Users will receive a notification with no sound. Default is $false
.OUTPUTS
    System.Boolean
.NOTES
    Author: Jake Morrison - @jakemorrison - http://techthoughts.info/
    This works with PowerShell Versions 5.1, 6.0, 6.1
    For a description of the Bot API, see this page: https://core.telegram.org/bots/api
    How do I get my channel ID? Use the getidsbot https://telegram.me/getidsbot
    How do I set up a bot and get a token? Use the BotFather https://t.me/BotFather
.COMPONENT
   PoshGram - https://github.com/techthoughts2/PoshGram
.FUNCTIONALITY
    https://core.telegram.org/bots/api#sendmessage
    Parameters 					Type 				Required 	Description
    chat_id 				    Integer or String 	Yes 		Unique identifier for the target chat or username of the target channel (in the format @channelusername)
    text 						String 				Yes 		Text of the message to be sent
    parse_mode 					String 				Optional 	Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message.
    disable_web_page_preview 	Boolean 			Optional 	Disables link previews for links in this message
    disable_notification 		Boolean 			Optional 	Sends the message silently. Users will receive a notification with no sound.
    reply_to_message_id 	    Integer 			Optional 	If the message is a reply, ID of the original message
#>
function Send-TelegramTextMessage {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true,
            HelpMessage = '#########:xxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxx')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [string]$BotToken, #you could set a token right here if you wanted
        [Parameter(Mandatory = $true,
            HelpMessage = '-#########')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [string]$ChatID, #you could set a Chat ID right here if you wanted
        [Parameter(Mandatory = $true,
            HelpMessage = 'Text of the message to be sent')]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [string]$Message,
        [Parameter(Mandatory = $false,
            HelpMessage = 'HTML vs Markdown for message formatting')]
        [ValidateSet("Markdown", "HTML")]
        [string]$ParseMode = "Markdown", #set to Markdown by default
        [Parameter(Mandatory = $false,
            HelpMessage = 'Disables link previews')]
        [bool]$Preview = $false, #set to false by default
        [Parameter(Mandatory = $false,
            HelpMessage = 'Sends the message silently')]
        [bool]$Notification = $false #set to false by default
    )
    #------------------------------------------------------------------------
    $results = $true #assume the best
    #------------------------------------------------------------------------
    $payload = @{
        "chat_id"                   = $ChatID;
        "text"                      = $Message
        "parse_mode"                = $ParseMode;
        "disable_web_page_preview"  = $Preview;
        "disable_notification"      = $Notification
    }#payload
    #------------------------------------------------------------------------
    try {
        Write-Verbose -Message "Sending message..."
        $eval = Invoke-RestMethod `
            -Uri ("https://api.telegram.org/bot{0}/sendMessage" -f $BotToken) `
            -Method Post `
            -ContentType "application/json" `
            -Body (ConvertTo-Json -Compress -InputObject $payload) `
            -ErrorAction Stop
        if (!($eval.ok -eq "True")) {
            Write-Warning -Message "Message did not send successfully"
            $results = $false
        }#if_StatusDescription
    }#try_messageSend
    catch {
        Write-Warning "An error was encountered sending the Telegram message:"
        Write-Error $_
        $results = $false
    }#catch_messageSend
    return $results
    #------------------------------------------------------------------------
}#function_Send-TelegramTextMessage

Declare a variável com o nome do Bot e a chave

  • Sendo simplista você vai precisar agora criar duas variáveis a com o nome do Token e o ID do chat
$bot = "token"
$chat = "-ID_do_chat"

Mande uma mensagem

  • Agora é só chamar a função e mandar a mensagem…
  • AAHH a máquina precisa ter acesso a internet.
  • Qualquer mensagem de erro a função vai tentar trazer a mensagem para te ajudar no troubleshooting.
$bot = "token"
$chat = "-ID_do_chat"
Send-TelegramTextMessage -BotToken $bot -ChatID $chat -Message "CASA CAIU"

Com isso agora temos um powershell que acessa uma API do Telegram e manda mensagem, junta isso em um JOB que é acionado por um alerta e vc tem o SQL ou outro sistema de monitoração mandando mensagens para seu celular.

Liberar toda a memória do servidor

Todos sabemos que o SQL é um consumidor de memória frenético, quanto mais memória disponível mais memória ele vai reservar para ele.

O que é um desenho “by default”, ele sempre fará isso afinal de contas ele precisa alocar as páginas de dados do seu banco em algum lugar.

Para resolver todos os seus problemas, existe uma forma de liberar toda a memória disponível de uma só vez do seu servidor e não é parando o serviço do SQL.

Para isso, você vai precisar o Visual Studio instalado, vamos criar um novo projeto dele…

Importante! Abra o Visual Studio como administrador !

Novo projeto de linha de comando

Escreva o nome que quiser para o app

Copie e cole o código abaixo no projeto:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class CriticalProcess
{
    [DllImport("ntdll.dll", SetLastError = true)]
    private static extern int NtSetInformationProcess(IntPtr hProcess, int processInformationClass, ref int processInformation, int processInformationLength);

    static void Main(string[] args)
    {
        int isCritical = 1;  // queremos que ele seja um processo crítico
        int BreakOnTermination = 0x1D;  // valor para BreakOnTermination (flag)

        Process.EnterDebugMode();  //acquire Debug Privileges

        // configurando o BreakOnTermination = 1 para o processo ativo
        NtSetInformationProcess(Process.GetCurrentProcess().Handle, BreakOnTermination, ref isCritical, sizeof(int));
    }
}

Se tudo ocorrer como esperado, dependendo da quantidade de memória do seu servidor isso pode demorar de alguns segundos a algumas horas.

Por mais que tenhamos criado uma aplicação de linha de comando a primeira parte do processo é bem gráfica e todos já tiveram o grande prazer de conhecer:

Ele vai gerar um DUMP de toda a memória para o arquivo de paginação e depois que a maquina reiniciar ele vai copiar esse arquivo de paginação para um arquivo chamado memory.dump

É só isso,,, execução e queda,,,

Agora falando sério: NUNCA !!!! JAMAIS !!!!! Simplesmente pegue o código de qualquer coisa que você encontra na internet e saia executando sem antes entender o que ele faz.

Esse exemplo é bem ridículo, mas imagina um script que você leu o por alto achando que vai resolver todos os seus problemas de backup, ou de fragmentação de índice e descobre que no meio tem um sp_msforeach_table com um sp_msforeach_db que trunca as tabelas, ou pior, alguém cria uma chave de criptografia e habilita TDE nas suas bases e depois força a remoção da chave,,,, a culpa é tão e somente sua! Você é o DBA é sua responsabilidade preservar os dados.

Tenha discernimento com o que você copia da internet e de onde copia essas informações.

SEMPRE LEIA e NUNCA EXECUTE DIRETAMENTE EM PRODUÇÃO !!!

Coisas que só o Powershell ISE faz por você

Tenho alguns clusters que estão em Multi-site, usando um Quorum em File Share para melhorar a disponibilidade.

Como o “Cluster Core” conta como voto na contagem do cluster, tenho preferencia na localização do recurso, já que todos os nós estão votando ele trabalha como voto de desempate.

Eu precisava monitorar um evento bem particular do cluster, quando o “Cluster Core Resource” alterasse de site ele deveria alarmar pelo SCOM, até ai tranquilo,,, faço um script no POSH o pessoal coloca para monitorar e pronto….


$API = new-object -comObject &quot;MOM.ScriptAPI&quot;
$bag = $api.CreatePropertyBag()
$resultado = (@(Get-ClusterGroup &quot;Cluster Group&quot; | Where-Object {$_.OwnerNode -like 'SERVIDOR*'}).count -eq 1)

if ($resultado -eq $true)
{
$bag.AddValue(&quot;State&quot;,&quot;Good&quot;)}
else
{
$bag.AddValue(&quot;State&quot;,&quot;Bad&quot;)}

$API.Return($bag)

Enquanto testava o script percebi que o Powershell ISE estava retornando mensagem de erro, fiquei revendo argumento, variável, comObject, mas não encontrei o erro…

Powershell ISE SCOM Script Error

Exception calling “Return” with “1” argument(s): “The handle is invalid. (Exception from HRESULT: 0x80070006 (E_HANDLE))”
At line:15 char:1
+ $API.Return($bag)
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation

A solução é bem simples:

  • Salve o arquivo em script e execute ele… o ISE não consegue interpretar o retorno do comObject.

O retorno é alguma coisa como:

<DataItem type=”System.PropertyBagData” time=”2015-06-24T12:18:40.1270337-03:00″ sourceHealthServiceId=”68A1F050-F975-9EE7-E0F3-C2CDE3445FD3″><Property Name=”State” VariantType=”8″>Bad</Property></DataItem>

Snapshot

Esse script não tem nada de complexo, ele serve mais para ajudar a criar de uma forma dinâmica o SnapShot de uma base.

Montei ele para funcionar da forma mais genérica possível, para bases com 1 arquivo até bases com 1000 arquivos.

ele vai renomear a extensão dos arquivos e vai criá-los no mesmo local dos arquivos originais.

DECLARE @ExecStr nvarchar(max)
, @DB sysname
, @Name sysname
, @Physical_Name sysname

set @DB = '' --Base que terá o SnapShot criado

DECLARE FileListCursor CURSOR FAST_FORWARD FOR
SELECT Name
, Physical_Name
FROM sys.master_files
where database_id = DB_ID(@DB)
 and type &lt;&gt; 1

OPEN FileListCursor

FETCH NEXT FROM FileListCursor INTO @Name, @Physical_Name

SELECT @ExecStr = N'CREATE DATABASE ' + @DB + '_SS ' +
N' ON ( NAME = ''' + @Name + N''' , FILENAME = ''' + + replace(replace(@Physical_Name, '.mdf', '.ss'),'.ndf', '.ss') + + N''')
 '

FETCH NEXT FROM FileListCursor INTO @Name, @Physical_Name

WHILE @@FETCH_STATUS &lt;&gt; -1
BEGIN
 SELECT @ExecStr = @ExecStr + N', ( NAME = ''' + @Name
 + ''' , FILENAME = ''' + replace(replace(@Physical_Name, '.mdf', '.ss'),'.ndf', '.ss') + ''')
 '
 FETCH NEXT FROM FileListCursor INTO @Name, @Physical_Name
END

SELECT @ExecStr = @ExecStr + N' AS SNAPSHOT OF [' + @DB +']'

--exec (@ExecStr) --executa o TSQL
select (@ExecStr) --gera o TSQL para execuçao manual

DEALLOCATE FileListCursor
GO

Essa semana tive que usar esta solução para aquela feature meia boca (feita pela metade) do AlwaysOn no SQL 2012.

Quando o link ou o SQL do site primário apresenta problemas a base secundária cai junto,,, esse Always (nem sempre) On no 2012 tem esse problema que, em teoria, foi corrigido no 2014.

Como a migração de SQL está descartada, surgiram algumas alternativas mas o melhor cenário foi:

  • Criar uma base oca;
  • Criar sinônimos;
  • Criar SnapShot.

Agora tenho um Job que fica monitorando a DM do AlwaysOn e garante que os sinônimos da base oca estão apontando para a base que está sendo replicada. Quando ocorrer uma falha, ele gera um SnapShot dessa base e altera os sinônimos para a base SnapShot. E fica monitorando o ambiente para inverter a situação quando a base replicada for restabelecida.

Esse POG funciona… e por incrível que pareça é bem rápido… nos testes a criação do SnapShot da base com 4TB demorou 12 seg e a troca dos sinônimos foi instantânea.

 

Contar VLF

Esse script é bem simples, ele vai acessar todas as bases da instância e vai contar quantos VLF´s existem em cada base.
Nada muito complexo.
Qual a importância disso? Muitos VLF´s podem acarretar em um restore muito lento, ou uma inicialização da base muito demorada.
Isso acontece, em muitos casos, por crescimento desordenado do transaction log (crescimento em porcentagem, ou em tamanhos pequenos).
Podemos colocar da seguinte forma: Falta de planejamento e atenção.
Quer saber mais sobre VLF? esse vídeo é bem legal.
Existe alguma forma de corrigir isso? Claro… Não é bonito…
Vamos fazer um quiz, coloquem um comentário com a maior quantidade de VLF´s que vocês tem no ambiente…


CREATE TABLE #temp (
 RecoveryUnitID VARCHAR(255) --essa coluna só existe a partir do 2012, comente esta linha se for executar do 2008r2 para traz
 , FileID VARCHAR(255)
 , FileSize VARCHAR(255)
 , StartOffset VARCHAR(255)
 , FSeqNo VARCHAR(255)
 , [Status] VARCHAR(255)
 , Parity VARCHAR(255)
 , CreateLSN VARCHAR(255)
 )

CREATE TABLE #DBCCResults (
 ServerName VARCHAR(255)
 , DBName VARCHAR(255)
 , VLF bigint
 )

EXEC master.dbo.sp_MSforeachdb
 @command1 = 'USE ? INSERT INTO #temp EXECUTE (''DBCC LOGINFO'')'
 , @command2 = 'INSERT INTO #DBCCResults SELECT @@SERVERNAME, ''?'', count(1) FROM #temp'
 , @command3 = 'TRUNCATE TABLE #temp'

 SELECT DISTINCT
 ServerName
 , DBName
 , VLF
 FROM #DBCCResults
 ORDER BY 3 DESC

 DROP TABLE #temp, #DBCCResults;

Testar Porta

Vocês sabem que se quiser testar uma porta TCP um dos métodos mais simples é basicamente um telnet Nome/IP porta.
Se o prompt sumir e o cursor ficar piscando a porta está respondendo (claro,, tirando todas as implicações de liberação de firewall e blá blá blá)
Eu precisava ficar fazendo um teste mais dinâmico, já que o telnet estabelece a conexão e espera uma intervenção para continuar eu queria apenas saber se a porta esta aberta ou não, estávamos tentando identificar uma falha se era no serviço ou na rede.
o script abaixo fica estabelecendo uma comunicação em um intervalo definido usando o socket TCP/IP estão podemos testar TCP e UDP bem no nível da camada e não da aplicação.
ele é bem simples, em qualquer momento que a porta não responda ele coloca a cor de fundo como vermelho.

function TestPort
{
    Param(
        [parameter(ParameterSetName='ComputerName', Position=0)]
        [string]
        $ComputerName,

        [parameter(ParameterSetName='IP', Position=0)]
        [System.Net.IPAddress]
        $IPAddress,

        [parameter(Mandatory=$true , Position=1)]
        [int]
        $Port,

        [parameter(Mandatory=$true, Position=2)]
        [ValidateSet(&quot;TCP&quot;, &quot;UDP&quot;)]
        [string]
        $Protocol
        )

    $RemoteServer = If ([string]::IsNullOrEmpty($ComputerName)) {$IPAddress} Else {$ComputerName};

    If ($Protocol -eq 'TCP')
    {
        $test = New-Object System.Net.Sockets.TcpClient;
        Try
        {
            Write-Host &quot;Connecting to &quot;$RemoteServer&quot;:&quot;$Port&quot; (TCP)..&quot;;
            $test.Connect($RemoteServer, $Port);
            Write-Host &quot;Connection successful&quot; -BackgroundColor Green;
        }
        Catch
        {
            Write-Host &quot;Connection failed&quot; -BackgroundColor Red;
        }
        Final
        {
            $test.Dispose();
        }
    }

    If ($Protocol -eq 'UDP')
    {
        $test = New-Object System.Net.Sockets.UdpClient;
        Try
        {
            Write-Host &quot;Connecting to &quot;$RemoteServer&quot;:&quot;$Port&quot; (UDP)..&quot;;
            $test.Connect($RemoteServer, $Port);
            Write-Host &quot;Connection successful&quot; -BackgroundColor Green;
        }
        Catch
        {
            Write-Host &quot;Connection failed&quot; -BackgroundColor Red;
        }
        Final
        {
            $test.Dispose();
        }
    }
}

A forma de testar ele é bem simples:

TestPort -ComputerName Nome/IP -Port 1433 -Protocol TCP 

Legal né? só que eu precisava ficar fazendo testes direto e reto e da forma acima ele não é um looping…
logo, para fazer da forma mais simples que conheço ficou assim:

$servidor = &quot;Nome/IP&quot;
while (1) {
    get-date #só pra saber quando executou
    TestPort -ComputerName $servidor -Port 1433 -Protocol TCP 
    sleep -seconds 1 #tempo de espera entre as execuções
} 

Recuperar DML através do Transaction Log

Todos sabem que quando é feita uma operação no banco muita coisa passa pelo Transaction Log. Pode ser de minimamente logado ou até quase absolutamente tudo e todo mundo sabe que é possível restaurar tudo do que foi feito através dos arquivos de backup.

O script abaixo é o tipo de script que vale muito ser guardado com todo o carinho naquele pendrive cheio de outros scripts… Ele lê o transaction log e gera as operações para reverter alguma modificação na tabela que está sendo pesquisada.

Ok, eu sei que existem aplicações que fazem isso, mas elas são pagas e dependendo do cenário você não pode instalar uma aplicação dessas em um servidor.

Também existe a opção de recover do banco e voltar o transaction até um momento antes daquela operação mas pode ser que seu banco tenha alguns TB´s ou você não possa simplesmente parar toda empresa por causa de alguns registros e comprometer todas as outras operações de outras partes dos sistemas.

Antes de mais nada quer entender como o script funciona? Leia este post do Paul Randal (b| t) você vai entender como funciona o fn_dblog e o fn_dump_dblog

Agora o script…

Originalmente o script está localizado aqui.

Ele possui direitos de cópia livres para modificação/publicação/etc.

/******
(c) Walk the Way, 2014 (see accompanying license)
To use this:
1) SQL Server Management Studio must be &quot;Run as administrator&quot; (or using an admin account)
2) The SQL login User must have SQL sysadmin Server Role
For running DBCC PAGE: it appears that the SQL User must have SQL sysadmin Server Role
For sys.fn_dblog: the SQL User must have SELECT permission for &quot;master&quot; in System Databases
For SQL 2008 uncomment line 1150 (DATE not suppported in SQL 2005)

The WTW Open License

Copyright (c) 2014 Walk the Way; all rights reserved, except as follows:

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the &quot;Software&quot;), to deal
in the Software as follows: the unrestricted right to use, copy, modify, merge,
publish, distribute, sublicense, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

modo de usar:

Recover_Modified_Data_Proc 'Nome_Da_Base', 'dbo.Tabela'

Recover_Modified_Data_Proc 'Nome_Da_Base', 'dbo.Tabela', '2014/07/16', '2014/07/18' --com periodo

******/
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[Recover_Modified_Data_Proc]')
AND TYPE IN ( N'FN', N'IF', N'TF', N'FS', N'FT', N'P' ))
SET NOEXEC ON

--&gt; Based on the work of Muhammad Imran generously published at:
--&gt; http://raresql.com/2012/02/01/how-to-recover-modified-records-from-sql-server-part-1/
--&gt;
--&gt; Uncomment/Comment block below for testing/development
/****************************************
SET NOEXEC OFF
IF OBJECT_ID (N'[@ResultTable]', N'U') IS NOT NULL DROP TABLE [@ResultTable]
IF OBJECT_ID('tempdb..#T1') IS NOT NULL DROP TABLE #T1
IF OBJECT_ID('tempdb..#temp_Data') IS NOT NULL DROP TABLE #temp_Data
IF OBJECT_ID('tempdb..#CTE') IS NOT NULL DROP TABLE #CTE
IF OBJECT_ID('tempdb..#TransIdUpdateList') IS NOT NULL DROP TABLE #TransIdUpdateList
IF OBJECT_ID('tempdb..#TransIdDeleteList') IS NOT NULL DROP TABLE #TransIdDeleteList
IF OBJECT_ID('tempdb..#TransIdAllList') IS NOT NULL DROP TABLE #TransIdAllList
IF OBJECT_ID('tempdb..#AllocIdList') IS NOT NULL DROP TABLE #AllocIdList
IF OBJECT_ID('tempdb..#SysFnDblog') IS NOT NULL DROP TABLE #SysFnDblog
IF OBJECT_ID('tempdb..#DelRows') IS NOT NULL DROP TABLE #DelRows
DECLARE @Database_Name NVARCHAR(MAX)
DECLARE @SchemaName_n_TableName NVARCHAR(MAX)
DECLARE @Date_From DATETIME
DECLARE @Date_To DATETIME
SET @Database_Name = 'Test'
SET @SchemaName_n_TableName = 'Student'
SET @Date_From = '2014/05/22' --'1900/01/01'
SET @Date_To = '2099/01/01' --'9999/12/31'
print @Date_From
print @Date_To
****************************************/
--&gt; Uncomment/Comment block below to create Stored Procedure
/****************************************/
IF OBJECT_ID('Recover_Modified_Data_Proc', 'P') IS NOT NULL
DROP PROC Recover_Modified_Data_Proc
GO
Create PROCEDURE Recover_Modified_Data_Proc
@Database_Name NVARCHAR(MAX),
@SchemaName_n_TableName NVARCHAR(MAX),
@Date_From DATETIME = '1900/01/01', --&gt; Null equivalent
@Date_To DATETIME = '9999/12/31' --&gt; largest date
AS

--&gt; For SQL 2005; PIVOT requires compatibility_level = 90
IF ((SELECT [compatibility_level] from sys.databases where [name] = @Database_Name) &lt; 90) BEGIN
RETURN -1
END
/****************************************/

DECLARE @Debug INT
SET @Debug = 0
DECLARE @parms NVARCHAR(1024)
DECLARE @Fileid INT
DECLARE @hex_pageid AS VARCHAR(MAX)
DECLARE @Pageid INT
DECLARE @Slotid INT
DECLARE @TransactionName VARCHAR(MAX)
DECLARE @CurrentLSN VARCHAR(MAX)
DECLARE @BeginTime DATETIME
DECLARE @RowLogContents0 VARBINARY(8000)
DECLARE @RowLogContents1 VARBINARY(8000)
DECLARE @RowLogContents2 VARBINARY(8000)
DECLARE @RowLogContents3 VARBINARY(8000)
DECLARE @RowLogContents3_Var VARCHAR(MAX)

DECLARE @RowLogContents4 VARBINARY(8000)
DECLARE @LogRecord VARBINARY(8000)
DECLARE @LogRecord_Var VARCHAR(MAX)

DECLARE @ConsolidatedPageID VARCHAR(MAX)
DECLARE @AllocUnitID BIGINT
DECLARE @TransactionID VARCHAR(MAX)
DECLARE @Operation VARCHAR(MAX)
DECLARE @DatabaseCollation VARCHAR(MAX)

--FOR @Operation ='LOP_MODIFY_COLUMNS'
DECLARE @RowLogData_Var VARCHAR(MAX)
DECLARE @RowLogData_Hex VARBINARY(MAX)

DECLARE @TotalFixedLengthData INT
DECLARE @FixedLength_Offset INT
DECLARE @VariableLength_Offset INT
DECLARE @VariableLength_Offset_Start INT
DECLARE @VariableLengthIncrease INT
DECLARE @FixedLengthIncrease INT
DECLARE @OldFixedLengthStartPosition INT
DECLARE @FixedLength_Loc INT
DECLARE @VariableLength_Loc INT
DECLARE @FixedOldValues VARBINARY(MAX)
DECLARE @FixedNewValues VARBINARY(MAX)
DECLARE @VariableOldValues VARBINARY(MAX)
DECLARE @VariableNewValues VARBINARY(MAX)

/* Pick The actual data
*/
DECLARE @temppagedata TABLE(
[RecordID] INT IDENTITY(1,1)
,[ParentObject] sysname
,[Object] sysname
,[Field] sysname
,[Value] sysname
)

DECLARE @pagedata TABLE(
[Page Index] int
,[DBCCid] int
,[ParentObject] sysname
,[Object] sysname
,[Field] sysname
,[Value] sysname
)

DECLARE @pageindex TABLE(
[PageIndexID] INT IDENTITY(1,1)
,[ConsolidatedPageID] VARCHAR(MAX)
,[Fileid] VARCHAR(MAX)
,[hex_pageid] VARCHAR(MAX)
,[Pageid] VARCHAR(MAX)
)

IF (@Debug &gt; 2) BEGIN
--select * from sys.fn_dblog(NULL, NULL)
--&gt; http://db4breakfast.blogspot.com/2013/03/fndblog-function-documentation.html
--&gt; IMPORTANT: [databasename] &gt; Properties &gt; Options &gt; Recovery Model: Full
--&gt; http://msdn.microsoft.com/en-us/library/ms189275.aspx --&gt; Recovery Models (SQL Server)
--&gt; http://technet.microsoft.com/en-us/library/ms189272.aspx
--&gt; ALTER DATABASE model SET RECOVERY FULL
--&gt; Immediately after switching from the simple recovery model to the full recovery model or bulk-logged recovery model, take a full or differential database backup to start the log chain.
--&gt; The switch to the full or bulk-logged recovery model takes effect only after the first data backup.
SELECT name, recovery_model_desc
FROM sys.databases
WHERE name = 'model' ;

select 'Log file usage'
SELECT name AS [File Name],
physical_name AS [Physical Name],
size/128.0 AS [Total Size in MB],
size/128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS int)/128.0 AS [Available Space In MB],
[growth], [file_id]
,B.cntr_value as &quot;LogFullPct&quot;
FROM sys.database_files A
INNER JOIN sys.dm_os_performance_counters B ON RTRIM(B.instance_name)+'_log' = A.name
WHERE type_desc = 'LOG'
AND B.counter_name LIKE 'Percent Log Used%'
AND B.instance_name not in ('_Total', 'mssqlsystemresource')
--SELECT instance_name as [Database],cntr_value as &quot;LogFullPct&quot;
-- FROM sys.dm_os_performance_counters A
-- WHERE counter_name LIKE 'Percent Log Used%'
-- AND instance_name not in ('_Total', 'mssqlsystemresource')
-- AND cntr_value &gt; 0;
END
IF (@Debug &gt; 3) BEGIN
select 'sys.fn_dblog(NULL, NULL)'
--&gt; LSN = Log Sequence Number
select [Current LSN],[Operation],[Context],[TRANSACTION ID],[Transaction Name],[Previous LSN],[AllocUnitId],[AllocUnitName],[Begin Time]
,[RowLog Contents 0],[RowLog Contents 1],[RowLog Contents 2],[RowLog Contents 3],[RowLog Contents 4],SUSER_SNAME([Transaction SID]) AS [Transaction Account]
from sys.fn_dblog(NULL, NULL)
where [TRANSACTION ID] IN (SELECT DISTINCT [TRANSACTION ID] FROM sys.fn_dblog(NULL, NULL) WHERE [Transaction Name] IN ('UPDATE','DELETE','INSERT'))
order by [Current LSN]
--select * from sys.allocation_units
END

--&gt; Get [TRANSACTION ID]s &amp; [Transaction Name]s for INSERTs, DELETEs, UPDATEs in the time range
-- This includes: [Operation]s of ('LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS','LOP_INSERT_ROWS','LOP_DELETE_ROWS')
/*Use this subquery to filter the date*/
SELECT DISTINCT [TRANSACTION ID],[Transaction Name],SUSER_SNAME([Transaction SID]) AS [Transaction Account]
INTO #TransIdAllList
FROM sys.fn_dblog(NULL, NULL)
WHERE [Context] IN ('LCX_NULL') AND [Operation] IN ('LOP_BEGIN_XACT')
AND [Transaction Name] IN ('UPDATE','INSERT','DELETE')
AND CONVERT(NVARCHAR(11),[Begin Time]) BETWEEN @Date_From AND @Date_To

--&gt; Now remove all ('UPDATE','INSERT','DELETE')_transactions that are NOT ('LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS','LOP_INSERT_ROWS','LOP_DELETE_ROWS')_transactions
DELETE FROM #TransIdAllList WHERE [TRANSACTION ID] IN
(SELECT DISTINCT [TRANSACTION ID]
--INTO #TransIdAllList
FROM sys.fn_dblog(NULL, NULL)
WHERE [TRANSACTION ID] IN (SELECT [TRANSACTION ID] FROM #TransIdAllList)
AND [Operation] NOT IN (NULL,'LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS','LOP_INSERT_ROWS','LOP_DELETE_ROWS'))

--&gt; Get [TRANSACTION ID]s for UPDATEs in time range; both 'LOP_MODIFY_ROW' &amp; 'LOP_MODIFY_COLUMNS'
SELECT [TRANSACTION ID],[Transaction Name]
INTO #TransIdUpdateList
FROM #TransIdAllList
WHERE [Transaction Name] IN ('UPDATE')

--&gt; Get [TRANSACTION ID]s for DELETEs in time range
-- WARNING: the @Date_To needs to include the DELETE for deleted UPDATEs
SELECT DISTINCT [TRANSACTION ID]
INTO #TransIdDeleteList
FROM #TransIdAllList
WHERE [Transaction Name] IN ('DELETE')

--&gt; Get appropriate [Allocation_unit_id]s
SELECT [Allocation_unit_id]
INTO #AllocIdList
FROM sys.allocation_units allocunits
INNER JOIN sys.partitions partitions ON
(allocunits.type IN (1, 3) AND partitions.hobt_id = allocunits.container_id)
OR (allocunits.type = 2 AND partitions.partition_id = allocunits.container_id)
WHERE object_id=object_ID('' + @SchemaName_n_TableName + '')

--&gt; Get the sys.fn_dblog record info for all transactions of concern
SELECT [Current LSN],[Operation],[Context],[PAGE ID],[AllocUnitId],[Slot ID],[TRANSACTION ID],[Transaction Name],[Begin Time],[RowLog Contents 0],[RowLog Contents 1],[RowLog Contents 2],[RowLog Contents 3],[RowLog Contents 4],[Log Record Length],[Log Record Fixed Length],[Log Record]
INTO #SysFnDblog
FROM sys.fn_dblog(NULL, NULL)
WHERE [TRANSACTION ID] IN (SELECT [TRANSACTION ID] FROM #TransIdAllList)
--select * from #SysFnDblog

--&gt; Get record info for deleted rows for UPDATEs that may have been deleted
SELECT [PAGE ID],[AllocUnitId],[Slot ID],[TRANSACTION ID],MAX([RowLog Contents 0]) AS [RowLog Contents 0] --&gt; removes NULLs
INTO #DelRows
FROM #SysFnDblog
WHERE [Operation] IN ('LOP_DELETE_ROWS')
AND [AllocUnitId] IN (SELECT * FROM #AllocIdList)
AND [TRANSACTION ID] IN (SELECT * FROM #TransIdDeleteList)
GROUP BY [PAGE ID],[AllocUnitId],[Slot ID],[TRANSACTION ID]

IF (@Debug &gt; 1) BEGIN
SELECT '#TransIdAllList'
SELECT * FROM #TransIdAllList
SELECT '#TransIdUpdateList'
SELECT * FROM #TransIdUpdateList
SELECT '#TransIdDeleteList'
SELECT * FROM #TransIdDeleteList
SELECT '#AllocIdList'
SELECT * FROM #AllocIdList
SELECT '#SysFnDblog '
SELECT * FROM #SysFnDblog
print 'select * from #DelRows'
SELECT '#DelRows'
SELECT * FROM #DelRows
END

DECLARE @ModifiedRawData TABLE(
[ID] INT IDENTITY(1,1)
,[PAGE ID] VARCHAR(MAX)
,[Slot ID] INT
,[AllocUnitId] BIGINT
,[Datum] VARCHAR(20)
,[TRANSACTION ID] VARCHAR(MAX)
,[Transaction Name] VARCHAR(MAX)
,[Current LSN] VARCHAR(MAX)
,[EntryID] INT DEFAULT -1
,[PrevEntry] INT DEFAULT -1
,[NextEntry] INT DEFAULT -1
,[ChainLevel] INT DEFAULT -1
,[Process] INT DEFAULT -1
,[Begin Time] DATETIME
,[RowLog Contents 0_var] VARCHAR(MAX)
,[RowLog Contents 0] VARBINARY(8000)
,[Slot Info] VARCHAR(MAX)
)

--&gt; Get UPDATEd records information
INSERT INTO @ModifiedRawData ([PAGE ID],[Slot ID],[AllocUnitId],[Datum],[TRANSACTION ID],[Transaction Name],[Current LSN],[Begin Time],[RowLog Contents 0_var])
SELECT A.[PAGE ID],A.[Slot ID],A.[AllocUnitId],'Reference' AS [Datum],A.[TRANSACTION ID]
,(SELECT [Transaction Name] FROM #TransIdUpdateList WHERE [TRANSACTION ID] = A.[TRANSACTION ID]) AS [Transaction Name] --&gt; always = 'UPDATE' here
,A.[Current LSN]
,(SELECT MAX([Begin Time]) FROM #SysFnDblog WHERE [TRANSACTION ID] = A.[TRANSACTION ID]) AS [Begin Time] --&gt; eliminates NULL values
,NULL AS [Value] --&gt; [RowLog Contents 0_var]
FROM #SysFnDblog A
WHERE A.[AllocUnitId] IN (SELECT * FROM #AllocIdList)
AND [Operation] IN ('LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS')
AND [Context] IN ('LCX_HEAP','LCX_CLUSTERED')
AND [TRANSACTION ID] IN (SELECT [TRANSACTION ID] FROM #TransIdUpdateList)
GROUP BY A.[PAGE ID],A.[Slot ID],A.[AllocUnitId],[Begin Time],[Transaction ID],[Transaction Name],A.[Current LSN]
ORDER BY [Slot ID],[Current LSN] --&gt; [Slot ID] is the record entry number
--SELECT * FROM @ModifiedRawData

--&gt; Get the indexing information for DBCC PAGE
INSERT INTO @pageindex ([ConsolidatedPageID],[Fileid],[hex_pageid],[Pageid])
SELECT DISTINCT [PAGE ID],NULL,NULL,NULL FROM @ModifiedRawData
UPDATE @pageindex
SET [Fileid] = SUBSTRING([ConsolidatedPageID],0,CHARINDEX(':',[ConsolidatedPageID])) -- Seperate File ID from Page ID
, [hex_pageid] = '0x'+ SUBSTRING([ConsolidatedPageID],CHARINDEX(':',[ConsolidatedPageID])+1,LEN([ConsolidatedPageID])) -- Seperate the page ID
UPDATE @pageindex
SET [Pageid] = CONVERT(INT,CAST('' AS XML).value('xs:hexBinary(substring(sql:column(&quot;[hex_pageid]&quot;),sql:column(&quot;t.pos&quot;)) )', 'varbinary(max)')) -- Convert Page ID from hex to integer
FROM (SELECT CASE SUBSTRING([hex_pageid], 1, 2) WHEN '0x' THEN 3 ELSE 0 END FROM @pageindex A WHERE A.[PageIndexID] = [PageIndexID]) AS t(pos)
--SELECT * FROM @pageindex

/*******************CURSOR START*********************/
--&gt; Get DBCC PAGE data for each [ConsolidatedPageID]
DECLARE Page_Data_Cursor CURSOR FOR
SELECT [Fileid],[Pageid]
FROM @pageindex
ORDER BY [Fileid],[Pageid]
/****************************************/
OPEN Page_Data_Cursor

FETCH NEXT FROM Page_Data_Cursor INTO @Fileid,@Pageid
WHILE @@FETCH_STATUS = 0
BEGIN
DELETE @temppagedata
--&gt; the next line requires cursor use due to augument requirements
INSERT INTO @temppagedata EXEC( 'DBCC PAGE(' + @DataBase_Name + ', ' + @Fileid + ', ' + @Pageid + ', 3) with tableresults,no_infomsgs;')
--&gt; Concatenante all DBCC PAGE pages into one file by [ConsolidatedPageID] using index [PageIndexID]
INSERT INTO @pagedata ([Page Index],[DBCCid],[ParentObject],[Object],[Field],[Value])
SELECT B.[PageIndexID],A.[RecordID],[ParentObject],[Object],[Field],[Value]
FROM @temppagedata A
JOIN @pageindex B ON B.[Pageid] = @Pageid AND B.[Fileid] = @Fileid
FETCH NEXT FROM Page_Data_Cursor INTO @Fileid,@Pageid
END

CLOSE Page_Data_Cursor
DEALLOCATE Page_Data_Cursor
/*******************CURSOR END*********************/

--&gt; Add the DBCC PAGE information OR DELETEd info to the UPDATEd records information
-- DBCC PAGE indexing is independent of [AllocUnitId]
UPDATE @ModifiedRawData
SET
[RowLog Contents 0_var] =
COALESCE( --&gt; look for DBCC PAGE record set
UPPER((SELECT REPLACE(STUFF(
(SELECT REPLACE(SUBSTRING([VALUE],CHARINDEX(':',[Value])+1,48),'†','')
FROM @pagedata C
WHERE B.[Page Index] = C.[Page Index] --&gt; EQUIVALENT to [ConsolidatedPageID]
AND A.[Slot ID] = LTRIM(RTRIM(SUBSTRING(C.[ParentObject],5,3)))
AND [Object] LIKE '%Memory Dump%'
GROUP BY [Value] FOR XML PATH('')
),1,1,'') ,' ','')
)) --&gt; next, look for deleted record
,UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;C.[RowLog Contents 0]&quot;) )', 'varchar(max)'))
,'ERROR: '+B.[ParentObject] --&gt; Missing [Slot ID] in &quot;DBCC PAGE&quot;, and no corresponding DELETE found
)
,[Slot Info] = COALESCE(B.[ParentObject],'Slot '+CONVERT(VARCHAR,C.[Slot ID])+' deleted')
FROM @ModifiedRawData A
INNER JOIN @pageindex D ON D.[ConsolidatedPageID] = A.[PAGE ID]
LEFT JOIN @pagedata B ON
B.[Page Index] = D.[PageIndexID]
AND A.[Slot ID] = LTRIM(RTRIM(SUBSTRING(B.[ParentObject],5,3)))
AND B.[Object] LIKE '%Memory Dump%'
LEFT JOIN #DelRows C ON
C.[PAGE ID]=[A].[PAGE ID]
AND C.[AllocUnitId] = A.[AllocUnitId]
AND C.[Slot ID] = A.[Slot ID]

--&gt; Convert the old data which is in string format to hex format (required).
UPDATE @ModifiedRawData SET [RowLog Contents 0] = CAST('' AS XML).value('xs:hexBinary(substring(sql:column(&quot;[RowLog Contents 0_var]&quot;), 0) )', 'varbinary(max)')
FROM @ModifiedRawData

IF (@Debug &gt; 0) BEGIN
select '@pageindex'
select * from @pageindex
select '@DBCC PAGE'
select * from @temppagedata
select '@pagedata'
select * from @pagedata --ORDER BY [Page ID],[AllocUnitId],[Current LSN],[DBCCid]
print 'select * from @ModifiedRawData1'
select '@ModifiedRawData1'
select * from @ModifiedRawData
END

DECLARE @PreliminaryRawData TABLE(
[ID] INT IDENTITY(1,1)
,[PAGE ID] VARCHAR(MAX)
,[Slot ID] INT
,[AllocUnitId] BIGINT
,[Datum] VARCHAR(20)
,[TRANSACTION ID] VARCHAR(MAX)
,[Transaction Name] VARCHAR(MAX)
,[Current LSN] VARCHAR(MAX)
,[Begin Time] DATETIME
,[RowLog Contents 0] VARBINARY(8000)
,[RowLog Contents 1] VARBINARY(8000)
,[RowLog Contents 2] VARBINARY(8000)
,[RowLog Contents 3] VARBINARY(8000)
,[RowLog Contents 4] VARBINARY(8000)
,[Log Record] VARBINARY(8000)
,[Operation] VARCHAR(MAX)
,[RowLog Contents 0_var] VARCHAR(MAX)
,[RowLog Contents 1_var] VARCHAR(MAX)
)

---Now we have the modifed data plus its slot ID , page ID and allocunit as well.
--After that we need to get the old values before modfication, these data are in chunks.
INSERT INTO @PreliminaryRawData
SELECT [PAGE ID],[Slot ID],[AllocUnitId],'Restoration' AS [Datum],[TRANSACTION ID]
,(SELECT [Transaction Name] FROM #TransIdAllList WHERE [TRANSACTION ID] = A.[TRANSACTION ID]) AS [Transaction Name]
,[Current LSN]
,(SELECT MAX([Begin Time]) FROM #SysFnDblog WHERE [TRANSACTION ID] = A.[TRANSACTION ID]) AS [Begin Time] --&gt; eliminates NULL values
,[RowLog Contents 0],[RowLog Contents 1],[RowLog Contents 2],[RowLog Contents 3],[RowLog Contents 4]
,SUBSTRING ([Log Record],[Log Record Fixed Length],([Log Record Length]+1)-([Log Record Fixed Length])) as [Log Record]
,[Operation]
,UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;[RowLog Contents 0]&quot;) )', 'varchar(max)')) AS [RowLog Contents 0_var] --&gt; New, added for reference; integrated below
,UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;[RowLog Contents 1]&quot;) )', 'varchar(max)')) AS [RowLog Contents 1_var] --&gt; New, added for reference; integrated below
FROM #SysFnDblog A
WHERE [AllocUnitId] IN (SELECT * FROM #AllocIdList)
AND [Operation] IN ('LOP_MODIFY_ROW','LOP_MODIFY_COLUMNS','LOP_INSERT_ROWS','LOP_DELETE_ROWS')
AND [Context] IN ('LCX_HEAP','LCX_CLUSTERED')
--AND [TRANSACTION ID] IN (SELECT [TRANSACTION ID] FROM #TransIdAllList) --&gt; same filter for #SysFnDblog
ORDER BY [Slot ID],[Transaction ID] DESC
--SELECT * FROM @PreliminaryRawData
/*
If the [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.
If the [Operation] Type is 'LOP_INSERT_ROWS' or 'LOP_DELETE_ROWS' then it is very simple to recover the data. The old data is in [RowLog Contents 0] Field.
*/
INSERT INTO @ModifiedRawData ([PAGE ID],[Slot ID],[AllocUnitId],[Datum],[TRANSACTION ID],[Transaction Name],[Current LSN],[Begin Time],[RowLog Contents 0_var],[Slot Info])
SELECT A.[PAGE ID],A.[Slot ID],A.[AllocUnitId],'Restoration' AS [Datum],A.[TRANSACTION ID],A.[Transaction Name],A.[Current LSN],A.[Begin Time]
,CASE WHEN A.[Operation] IN ('LOP_INSERT_ROWS','LOP_DELETE_ROWS') THEN
(SELECT UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;A.[RowLog Contents 0]&quot;) )','varchar(max)'))) --&gt; for INSERTs and/or DELETEs with no UPDATEs (@ModifiedRawData is empty when this update starts)
ELSE --&gt; look for the UPDATE mate from the previous record pull
REPLACE(UPPER(B.[RowLog Contents 0_var]),UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;A.[RowLog Contents 1]&quot;) )','varchar(max)')),UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;A.[RowLog Contents 0]&quot;) )','varchar(max)')))
END AS [RowLog Contents 0_var]
,B.[Slot Info]
FROM @PreliminaryRawData A
LEFT JOIN @ModifiedRawData B ON B.[Current LSN]=A.[Current LSN]
WHERE A.[Operation] IN ('LOP_MODIFY_ROW','LOP_INSERT_ROWS','LOP_DELETE_ROWS')

-- Convert the old data which is in string format to hex format (required).
UPDATE @ModifiedRawData SET [RowLog Contents 0] = CAST('' AS XML).value('xs:hexBinary(substring(sql:column(&quot;[RowLog Contents 0_var]&quot;), 0) )', 'varbinary(max)')
FROM @ModifiedRawData
WHERE [RowLog Contents 0] IS NULL

IF (@Debug &gt; 0) BEGIN
select '@PreliminaryRawData'
select * from @PreliminaryRawData
print 'select * from @ModifiedRawData2_row_ops'
select '@ModifiedRawData2_row_ops'
select * from @ModifiedRawData
END
--&gt; remove these records since their processing is complete
--DELETE FROM @PreliminaryRawData WHERE [Operation] IN ('LOP_MODIFY_ROW','LOP_INSERT_ROWS','LOP_DELETE_ROWS')
DELETE FROM @PreliminaryRawData WHERE [Operation] IN ('LOP_INSERT_ROWS','LOP_DELETE_ROWS')
IF (@Debug &gt; 0) BEGIN
print 'select * from @PreliminaryRawData_remaining'
select '@PreliminaryRawData_remaining'
select * from @PreliminaryRawData
END

/*******************CURSOR START*********************/
---Now we have modifed data plus its slot ID , page ID and allocunit as well.
--After that we need to get the old values before modfication, these data are in chunks.
IF (@Debug = 0) BEGIN
DECLARE Page_Data_Cursor CURSOR FOR
SELECT [PAGE ID],[Slot ID],[AllocUnitId],[TRANSACTION ID],[Transaction Name],[Current LSN],[Begin Time],[RowLog Contents 0],[RowLog Contents 1],[RowLog Contents 2],[RowLog Contents 3],[RowLog Contents 4],[Log Record],[Operation]
FROM @PreliminaryRawData
WHERE [Operation] IN ('LOP_MODIFY_COLUMNS')
ORDER BY [Slot ID],[TRANSACTION ID] DESC
END
ELSE
BEGIN
DECLARE Page_Data_Cursor CURSOR FOR
SELECT [PAGE ID],[Slot ID],[AllocUnitId],[TRANSACTION ID],[Transaction Name],[Current LSN],[Begin Time],[RowLog Contents 0],[RowLog Contents 1],[RowLog Contents 2],[RowLog Contents 3],[RowLog Contents 4],[Log Record],[Operation]
FROM @PreliminaryRawData
ORDER BY [Slot ID],[TRANSACTION ID] DESC
END

/****************************************/

OPEN Page_Data_Cursor

FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID,@Slotid,@AllocUnitID,@TransactionID,@TransactionName,@CurrentLSN,@BeginTime,@RowLogContents0,@RowLogContents1,@RowLogContents2,@RowLogContents3,@RowLogContents4,@LogRecord,@Operation
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Operation IN ('LOP_MODIFY_ROW','LOP_INSERT_ROWS','LOP_DELETE_ROWS')
BEGIN
-- To see the debug printout, comment out the &quot;DELETE FROM @PreliminaryRawData...&quot; line above
IF (@Debug &gt; 1) BEGIN
PRINT 'ConsolidatedPageID = '+@ConsolidatedPageID
PRINT 'Slotid = '+CONVERT(nvarchar,@Slotid)
PRINT 'AllocUnitID = '+CONVERT(nvarchar,@AllocUnitID)
PRINT 'TransactionID = '+@TransactionID
PRINT 'BeginTime = '+CONVERT(nvarchar,@BeginTime,121)
PRINT 'TransactionName = '+@TransactionName
DECLARE @RowLogContents0_var VARCHAR(MAX)
DECLARE @RowLogContents1_var VARCHAR(MAX)
SET @RowLogContents0_var=(SELECT UPPER(CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents0&quot;) )', 'varchar(max)')))
SET @RowLogContents1_var=(SELECT UPPER(CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents1&quot;) )', 'varchar(max)')))
PRINT 'RowLogContents0 = '+@RowLogContents0_var
PRINT 'RowLogContents1 = '+@RowLogContents1_var
IF (@TransactionName='UPDATE') BEGIN
DECLARE @RowLogContents0_var1 VARCHAR(MAX)
DECLARE @RowLogContents0_var2 VARCHAR(MAX)
DECLARE @RowLogContents1_var0 VARCHAR(MAX)
DECLARE @RowLogContents0_var0 VARCHAR(MAX)
SET @RowLogContents0_var1=(SELECT [RowLog Contents 0_var]
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN AND [Datum]='Reference')
SET @RowLogContents1_var0=(SELECT UPPER(CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents1&quot;) )','varchar(max)'))
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN AND [Datum]='Reference')
SET @RowLogContents0_var0=(SELECT UPPER(CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents0&quot;) )','varchar(max)'))
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN AND [Datum]='Reference')
SET @RowLogContents0_var2=(SELECT REPLACE(UPPER([RowLog Contents 0_var]),UPPER(CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents1&quot;) )','varchar(max)')),UPPER(CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents0&quot;) )','varchar(max)')))
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN AND [Datum]='Reference')
PRINT 'RowLogContents0_var1 = '+@RowLogContents0_var1
PRINT 'RowLogContents1_var0 = '+@RowLogContents1_var0
PRINT 'RowLogContents0_var0 = '+@RowLogContents0_var0
PRINT 'RowLogContents0_var2 = '+@RowLogContents0_var2
PRINT 'RowLogContents0_var2a= '+REPLACE(@RowLogContents0_var1,@RowLogContents1_var0,@RowLogContents0_var0)
END
PRINT ''
--DECLARE @LSN VARCHAR(50)
--DECLARE @LSN0 VARCHAR(50)
--DECLARE @LSN1 VARCHAR(50)
--DECLARE @LSN2 VARCHAR(50)
--DECLARE @LSN_int1 INTEGER
--DECLARE @LSN_int2 INTEGER
--SET @LSN=CONVERT(VARCHAR,@CurrentLSN)
--SET @LSN_int1=CHARINDEX(':',@LSN,1)
--SET @LSN_int2=CHARINDEX(':',@LSN,@LSN_int1+1)
--SET @LSN0=SUBSTRING(@LSN,1,@LSN_int1-1)
--SET @LSN1=SUBSTRING(@LSN,@LSN_int1+1,@LSN_int2-@LSN_int1-1)
--SET @LSN2=SUBSTRING(@LSN,@LSN_int2+1,LEN(@LSN))
--PRINT 'CurrentLSN='+@LSN0+':'+@LSN1+':'+@LSN2

--&gt; SQL2005:CAST('' as xml).value('xs:hexBinary(substring(sql:variable(&quot;@CurrentLSN&quot;),1,8))','varbinary(max)') == SQL2008:CONVERT(VARBINARY,SUBSTRING(@CurrentLSN,1,8),2)
--DECLARE @str VARCHAR(50)
--SET @str=CONVERT(VARCHAR,CONVERT(INT,CAST('' as xml).value('xs:hexBinary(substring(sql:variable(&quot;@CurrentLSN&quot;),1,8))','varbinary(max)')))+':'+
-- CONVERT(VARCHAR,CONVERT(INT,CAST('' as xml).value('xs:hexBinary(substring(sql:variable(&quot;@CurrentLSN&quot;),10,8))','varbinary(max)')))+':'+
-- CONVERT(VARCHAR,CONVERT(INT,CAST('' as xml).value('xs:hexBinary(substring(sql:variable(&quot;@CurrentLSN&quot;),19,4))','varbinary(max)')))
--PRINT 'CurrentLSN_base10='+@str
--DECLARE @int BIGINT
--SET @int=CONVERT(BIGINT,CONVERT(VARCHAR,CONVERT(INT,CAST('' as xml).value('xs:hexBinary(substring(sql:variable(&quot;@CurrentLSN&quot;),1,8))','varbinary(max)')))+
-- RIGHT('0000000000'+CONVERT(VARCHAR,CONVERT(INT,CAST('' as xml).value('xs:hexBinary(substring(sql:variable(&quot;@CurrentLSN&quot;),10,8))','varbinary(max)'))),10)+
-- RIGHT('00000'+CONVERT(VARCHAR,CONVERT(INT,CAST('' as xml).value('xs:hexBinary(substring(sql:variable(&quot;@CurrentLSN&quot;),19,4))','varbinary(max)'))),5) )
--PRINT 'CurrentLSN_int='+CONVERT(VARCHAR,@int)
END
END

IF @Operation ='LOP_MODIFY_COLUMNS'
BEGIN

/* If the @Operation Type is 'LOP_MODIFY_ROW' then we need to follow a different procedure to recover modified
.Because this time the data is also in chunks but merge with the data log.
*/
--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.
SET @RowLogContents3_Var = CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents3&quot;) )', 'varchar(max)')
SET @LogRecord_Var = CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@LogRecord&quot;))', 'varchar(max)')

---First get the modifed data chunks in string format
SET @RowLogData_Var = SUBSTRING(@LogRecord_Var, CHARINDEX(@RowLogContents3_Var,@LogRecord_Var)+LEN(@RowLogContents3_Var) ,LEN(@LogRecord_Var))
--Then convert it into the hex values.
SELECT @RowLogData_Hex = CAST('' AS XML).value('xs:hexBinary( substring(sql:variable(&quot;@RowLogData_Var&quot;),0) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@RowLogData_Var, 1, 2)WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)

-- Before recovering the modfied data we need to get the total fixed length data size and start position of the varaible data

SELECT @TotalFixedLengthData = CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0] , 2 + 1, 2))))
,@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))
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN

SET @FixedLength_Offset = CONVERT(BINARY(2),REVERSE(CONVERT(BINARY(4),(@RowLogContents0))))--)
SET @VariableLength_Offset = CONVERT(INT,CONVERT(BINARY(2),REVERSE(@RowLogContents0)))

/* We already have modified data chunks in @RowLogData_Hex but this data is in merge format (modified plus actual data)
So , here we need [Row Log Contents 1] field , because in this field we have the data length both the modified and actual data
so this length will help us to break it into original and modified data chunks.
*/
SET @FixedLength_Loc = CONVERT(INT,SUBSTRING(@RowLogContents1,1,1))
SET @VariableLength_Loc = CONVERT(INT,SUBSTRING(@RowLogContents1,3,1))

/*First , we need to break Fix length data actual with the help of data length */
SET @OldFixedLengthStartPosition= CHARINDEX(@RowLogContents4,@RowLogData_Hex)
SET @FixedOldValues = SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition,@FixedLength_Loc)
SET @FixedLengthIncrease = (CASE WHEN (Len(@FixedOldValues)%4) = 0 THEN 1 ELSE (4-(LEN(@FixedOldValues)%4)) END)
/*After that , we need to break Fix length data modified data with the help of data length */
SET @FixedNewValues =SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition+@FixedLength_Loc+@FixedLengthIncrease,@FixedLength_Loc)

/*Same we need to break the variable data with the help of data length*/
SET @VariableOldValues =SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition+@FixedLength_Loc+@FixedLengthIncrease+@FixedLength_Loc+(@FixedLengthIncrease),@VariableLength_Loc)
SET @VariableLengthIncrease = (CASE WHEN (LEN(@VariableOldValues)%4) = 0 THEN 1 ELSE (4-(Len(@VariableOldValues)%4))+1 END)
SET @VariableOldValues = (Case When @VariableLength_Loc = 1 Then @VariableOldValues+0x00 else @VariableOldValues end)

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)

/*here we need to replace the fixed length &amp; variable length actaul data with modifed data
*/

SELECT @VariableNewValues=CASE
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)+1),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)+1)
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues) ),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues))
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-1),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-1)--3 --Substring(@VariableNewValues,0,Len(@VariableNewValues)-1)
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-2),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-2)
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-3),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-3) --5--Substring(@VariableNewValues,0,Len(@VariableNewValues)-3)
END
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN

INSERT INTO @ModifiedRawData ([PAGE ID],[Slot ID],[AllocUnitId],[Datum],[TRANSACTION ID],[Transaction Name],[Current LSN],[Begin Time],[RowLog Contents 0_var],[RowLog Contents 0],[Slot Info])
SELECT @ConsolidatedPageID AS [PAGE ID],@Slotid AS [Slot ID],@AllocUnitID AS [AllocUnitId],'Restoration' AS [Datum],@TransactionID AS [TRANSACTION ID],@TransactionName AS [Transaction Name],@CurrentLSN AS [Current LSN],@BeginTime AS [Begin Time]
,NULL --&gt; [RowLog Contents 0_var]
,ISNULL( --&gt; due to chained updates; this only works on the most recent update
CAST(REPLACE(SUBSTRING([RowLog Contents 0],0,@TotalFixedLengthData+1),@FixedNewValues, @FixedOldValues) AS VARBINARY(max))
+ SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 1, 2)
+ SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 3, CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 1, 2))))/8.0)))
+ 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)
+ SUBSTRING([RowLog Contents 0],@VariableLength_Offset_Start,(@VariableLength_Offset-(@VariableLength_Offset_Start-1)))
+ CAST(REPLACE(SUBSTRING([RowLog Contents 0],@VariableLength_Offset+1,Len(@VariableNewValues))
,@VariableNewValues,@VariableOldValues) AS VARBINARY)
+ Substring([RowLog Contents 0],@VariableLength_Offset+Len(@VariableNewValues)+1,LEN([RowLog Contents 0]))
,[RowLog Contents 0]) --&gt; [RowLog Contents 0]
,[Slot Info]
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN

--&gt; Convert the old data which is in hex format to string format (not required).
UPDATE @ModifiedRawData SET [RowLog Contents 0_var] = CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;[RowLog Contents 0]&quot;) )', 'varchar(max)')
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN
END

FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID,@Slotid,@AllocUnitID,@TransactionID,@TransactionName,@CurrentLSN,@BeginTime,@RowLogContents0,@RowLogContents1,@RowLogContents2,@RowLogContents3,@RowLogContents4,@LogRecord,@Operation
END

CLOSE Page_Data_Cursor
DEALLOCATE Page_Data_Cursor
/*******************CURSOR END*********************/
------------------------------------------------------------------------------------------------------------
DECLARE @ModRawDataLoop INT
--&gt; Add sequence index number(s)
UPDATE A SET [EntryID] = B.[index] FROM @ModifiedRawData A
JOIN (SELECT ROW_NUMBER() OVER (ORDER BY [Current LSN],[Datum] DESC) AS [index], [ID] FROM @ModifiedRawData) B ON
B.[ID] = A.[ID]
--&gt; Add backward reference(s) and forward reference(s)
UPDATE A SET
[PrevEntry] =
ISNULL((SELECT MAX(B.[EntryID]) AS [EntryID] FROM @ModifiedRawData B
WHERE B.[Slot ID] = A.[Slot ID] AND B.[EntryID] &lt; A.[EntryID]),0)
,[NextEntry] =
ISNULL((SELECT MIN(B.[EntryID]) AS [EntryID] FROM @ModifiedRawData B
WHERE B.[Slot ID] = A.[Slot ID] AND B.[EntryID] &gt; A.[EntryID]),0)
FROM @ModifiedRawData A
--&gt; Add chain level(s)
UPDATE A SET [ChainLevel] = B.[index] FROM @ModifiedRawData A
JOIN (SELECT DENSE_RANK() OVER (PARTITION BY [SLOT ID] ORDER BY [Current LSN])-1 AS [index], [ID] FROM @ModifiedRawData) B ON
B.[ID] = A.[ID]
--&gt; Set the process order(s) --&gt; the reverse of the [ChainLevel]
UPDATE A SET
[Process] = CASE WHEN ([ChainLevel] = 0) THEN 0
ELSE (SELECT MAX([ChainLevel]) FROM @ModifiedRawData B WHERE B.[Slot ID] = A.[Slot ID]) - [ChainLevel]
END
FROM @ModifiedRawData A
--select * from @ModifiedRawData order by [EntryID]

--&gt; Get the chained UPDATE records (forward in time) to locate the replaced &quot;Reference&quot; record(s) (chained UPDATEs)
SELECT B.* INTO #T1
FROM @ModifiedRawData A
INNER JOIN @ModifiedRawData B ON B.[Transaction Name] = 'UPDATE'
AND B.[NextEntry] = A.[EntryID]
AND B.[Datum] = 'Reference' AND A.[Datum] = 'Restoration'
--AND B.[Current LSN] &lt;&gt; A.[Current LSN] --&gt; should always be true
SET @ModRawDataLoop = @@ROWCOUNT
--select * from #T1

IF (@ModRawDataLoop &gt; 0) BEGIN
WHILE (@ModRawDataLoop &gt; 0) BEGIN
/*
If the [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.
*/
--&gt; First, change the byte of [RowLog Contents 0_var] that may differ from the first byte of [RowLog Contents 1_var] (I don't know why these don't match sometimes)
--&gt; Fix the copy before it is restored back into the applicable records (next)
UPDATE #T1 SET
[RowLog Contents 0_var] =
SUBSTRING(B.[RowLog Contents 0_var],1,PATINDEX('%'+SUBSTRING(A.[RowLog Contents 1_var],3,LEN(A.[RowLog Contents 1_var]))+'%',B.[RowLog Contents 0_var])-3)
+SUBSTRING(A.[RowLog Contents 1_var],1,2)
+SUBSTRING(B.[RowLog Contents 0_var],PATINDEX('%'+SUBSTRING(A.[RowLog Contents 1_var],3,LEN(A.[RowLog Contents 1_var]))+'%',B.[RowLog Contents 0_var]),LEN(B.[RowLog Contents 0_var]))
,[RowLog Contents 0] =
SUBSTRING(B.[RowLog Contents 0],1,PATINDEX('%'+SUBSTRING(A.[RowLog Contents 1_var],3,LEN(A.[RowLog Contents 1_var]))+'%',B.[RowLog Contents 0_var])/2-1)
+SUBSTRING(A.[RowLog Contents 1],1,1)
+SUBSTRING(B.[RowLog Contents 0],PATINDEX('%'+SUBSTRING(A.[RowLog Contents 1_var],3,LEN(A.[RowLog Contents 1_var]))+'%',B.[RowLog Contents 0_var])/2+1,LEN(B.[RowLog Contents 0]))
FROM @PreliminaryRawData A
INNER JOIN #T1 C ON C.[Current LSN] = A.[Current LSN]
INNER JOIN @ModifiedRawData B ON B.[EntryID] = C.[NextEntry] --&gt; source location
WHERE A.[Operation] IN ('LOP_MODIFY_ROW') AND C.[Process] = 1
--select * from #T1
--&gt; Restore (one level) the replaced (and therefore lost) &quot;Reference&quot; information (chained UPDATEs)
-- for both the &quot;Reference&quot; and &quot;Restoration&quot; records, and the original source record
UPDATE @ModifiedRawData SET
[RowLog Contents 0_var] = B.[RowLog Contents 0_var]
,[RowLog Contents 0] = B.[RowLog Contents 0]
FROM @ModifiedRawData A
INNER JOIN #T1 B ON A.[EntryID] IN (B.[EntryID],B.[PrevEntry],B.[NextEntry])
WHERE B.[Process] = 1
--select * from @ModifiedRawData order by [EntryID]
--&gt; Now, the substitution will work, so update [RowLog Contents 0_var]
UPDATE @ModifiedRawData SET
[RowLog Contents 0_var] =
--&gt; look for the UPDATE mate from the previous record pull
REPLACE(UPPER(B.[RowLog Contents 0_var]),UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;A.[RowLog Contents 1]&quot;) )','varchar(max)')),UPPER(CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;A.[RowLog Contents 0]&quot;) )','varchar(max)')))
,[RowLog Contents 0] = NULL
FROM @PreliminaryRawData A
INNER JOIN @ModifiedRawData B ON B.[Current LSN]=A.[Current LSN]
INNER JOIN #T1 C ON C.[PrevEntry] = B.[EntryID]
WHERE A.[Operation] IN ('LOP_MODIFY_ROW') AND B.[Process] = 1

-- Convert the old data which is in string format to hex format (required).
UPDATE @ModifiedRawData SET
[RowLog Contents 0] = CAST('' AS XML).value('xs:hexBinary(substring(sql:column(&quot;[RowLog Contents 0_var]&quot;), 0) )', 'varbinary(max)')
FROM @ModifiedRawData
WHERE [RowLog Contents 0] IS NULL
SET @ModRawDataLoop = @@ROWCOUNT
--print '@ModRawDataLoop (LOP_MODIFY_ROW)= '+CONVERT(varchar,@ModRawDataLoop)
--select * from @ModifiedRawData order by [EntryID]

/*******************CURSOR START*********************/
---Now we have modifed data plus its slot ID , page ID and allocunit as well.
--After that we need to get the old values before modfication, these data are in chunks.
DECLARE Page_Data_Cursor CURSOR FOR
SELECT A.[PAGE ID],A.[Slot ID],A.[AllocUnitId],A.[TRANSACTION ID],A.[Transaction Name],A.[Current LSN],A.[Begin Time],A.[RowLog Contents 0],[RowLog Contents 1],[RowLog Contents 2],[RowLog Contents 3],[RowLog Contents 4],[Log Record],[Operation]
FROM @PreliminaryRawData A
INNER JOIN @ModifiedRawData B ON B.[Current LSN] = A.[Current LSN] --&gt; skip this section if there are no updates
WHERE [Operation] IN ('LOP_MODIFY_COLUMNS') AND B.[Process] = 1 AND B.[Datum] = 'Restoration'
ORDER BY A.[Slot ID],A.[TRANSACTION ID] DESC

SET @ModRawDataLoop = 0
/****************************************/

OPEN Page_Data_Cursor

FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID,@Slotid,@AllocUnitID,@TransactionID,@TransactionName,@CurrentLSN,@BeginTime,@RowLogContents0,@RowLogContents1,@RowLogContents2,@RowLogContents3,@RowLogContents4,@LogRecord,@Operation
WHILE @@FETCH_STATUS = 0
BEGIN
--IF @Operation ='LOP_MODIFY_COLUMNS'
BEGIN

/* If the @Operation Type is 'LOP_MODIFY_ROW' then we need to follow a different procedure to recover modified
.Because this time the data is also in chunks but merge with the data log.
*/
--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.
SET @RowLogContents3_Var = CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@RowLogContents3&quot;) )', 'varchar(max)')
SET @LogRecord_Var = CAST('' AS XML).value('xs:hexBinary(sql:variable(&quot;@LogRecord&quot;))', 'varchar(max)')

---First get the modifed data chunks in string format
SET @RowLogData_Var = SUBSTRING(@LogRecord_Var, CHARINDEX(@RowLogContents3_Var,@LogRecord_Var)+LEN(@RowLogContents3_Var) ,LEN(@LogRecord_Var))
--Then convert it into the hex values.
SELECT @RowLogData_Hex = CAST('' AS XML).value('xs:hexBinary( substring(sql:variable(&quot;@RowLogData_Var&quot;),0) )', 'varbinary(max)')
FROM (SELECT CASE SUBSTRING(@RowLogData_Var, 1, 2)WHEN '0x' THEN 3 ELSE 0 END) AS t(pos)

-- Before recovering the modfied data we need to get the total fixed length data size and start position of the varaible data

SELECT @TotalFixedLengthData = CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0] , 2 + 1, 2))))
,@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))
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN

SET @FixedLength_Offset = CONVERT(BINARY(2),REVERSE(CONVERT(BINARY(4),(@RowLogContents0))))--)
SET @VariableLength_Offset = CONVERT(INT,CONVERT(BINARY(2),REVERSE(@RowLogContents0)))

/* We already have modified data chunks in @RowLogData_Hex but this data is in merge format (modified plus actual data)
So , here we need [Row Log Contents 1] field , because in this field we have the data length both the modified and actual data
so this length will help us to break it into original and modified data chunks.
*/
SET @FixedLength_Loc = CONVERT(INT,SUBSTRING(@RowLogContents1,1,1))
SET @VariableLength_Loc = CONVERT(INT,SUBSTRING(@RowLogContents1,3,1))

/*First , we need to break Fix length data actual with the help of data length */
SET @OldFixedLengthStartPosition= CHARINDEX(@RowLogContents4,@RowLogData_Hex)
SET @FixedOldValues = SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition,@FixedLength_Loc)
SET @FixedLengthIncrease = (CASE WHEN (Len(@FixedOldValues)%4) = 0 THEN 1 ELSE (4-(LEN(@FixedOldValues)%4)) END)
/*After that , we need to break Fix length data modified data with the help of data length */
SET @FixedNewValues =SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition+@FixedLength_Loc+@FixedLengthIncrease,@FixedLength_Loc)

/*Same we need to break the variable data with the help of data length*/
SET @VariableOldValues =SUBSTRING(@RowLogData_Hex,@OldFixedLengthStartPosition+@FixedLength_Loc+@FixedLengthIncrease+@FixedLength_Loc+(@FixedLengthIncrease),@VariableLength_Loc)
SET @VariableLengthIncrease = (CASE WHEN (LEN(@VariableOldValues)%4) = 0 THEN 1 ELSE (4-(Len(@VariableOldValues)%4))+1 END)
SET @VariableOldValues = (Case When @VariableLength_Loc = 1 Then @VariableOldValues+0x00 else @VariableOldValues end)

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)

/*here we need to replace the fixed length &amp; variable length actaul data with modifed data
*/

SELECT @VariableNewValues=CASE
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)+1),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)+1)
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues) ),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues))
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-1),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-1)--3 --Substring(@VariableNewValues,0,Len(@VariableNewValues)-1)
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-2),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-2)
WHEN Charindex(Substring(@VariableNewValues,0,Len(@VariableNewValues)-3),[RowLog Contents 0])&lt;&gt;0 Then Substring(@VariableNewValues,0,Len(@VariableNewValues)-3) --5--Substring(@VariableNewValues,0,Len(@VariableNewValues)-3)
END
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN

UPDATE @ModifiedRawData SET
[RowLog Contents 0] =
CAST(REPLACE(SUBSTRING([RowLog Contents 0],0,@TotalFixedLengthData+1),@FixedNewValues, @FixedOldValues) AS VARBINARY(max))
+ SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 1, 2)
+ SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 3, CONVERT(INT, ceiling(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], @TotalFixedLengthData + 1, 2))))/8.0)))
+ 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)
+ SUBSTRING([RowLog Contents 0],@VariableLength_Offset_Start,(@VariableLength_Offset-(@VariableLength_Offset_Start-1)))
+ CAST(REPLACE(SUBSTRING([RowLog Contents 0],@VariableLength_Offset+1,Len(@VariableNewValues))
,@VariableNewValues,@VariableOldValues) AS VARBINARY)
+ Substring([RowLog Contents 0],@VariableLength_Offset+Len(@VariableNewValues)+1,LEN([RowLog Contents 0]))
,[RowLog Contents 0_var] = NULL
FROM @ModifiedRawData
WHERE [Current LSN]=@CurrentLSN AND [Process] = 1 AND [Datum] = 'Restoration'

--&gt; Convert the old data which is in hex format to string format (not required).
UPDATE @ModifiedRawData SET [RowLog Contents 0_var] = CAST('' AS XML).value('xs:hexBinary(sql:column(&quot;[RowLog Contents 0]&quot;) )', 'varchar(max)')
FROM @ModifiedRawData
WHERE [RowLog Contents 0_var] IS NULL
SET @ModRawDataLoop = @ModRawDataLoop + @@ROWCOUNT

END

FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID,@Slotid,@AllocUnitID,@TransactionID,@TransactionName,@CurrentLSN,@BeginTime,@RowLogContents0,@RowLogContents1,@RowLogContents2,@RowLogContents3,@RowLogContents4,@LogRecord,@Operation
END
--print '@ModRawDataLoop (LOP_MODIFY_COLUMNS)= '+CONVERT(varchar,@ModRawDataLoop)

CLOSE Page_Data_Cursor
DEALLOCATE Page_Data_Cursor
/*******************CURSOR END*********************/
--select * from @ModifiedRawData order by [EntryID]

--&gt; Update the process order
UPDATE #T1 SET [Process] = [Process] - 1
FROM #T1
WHERE [Process] &gt; 0
SET @ModRawDataLoop = (SELECT COUNT(*) FROM #T1 WHERE [Process] &gt; 0)
--print '@ModRawDataLoop (remaining)= '+CONVERT(varchar,@ModRawDataLoop)

IF (@ModRawDataLoop &gt; 0) BEGIN
--&gt; Update the process order
UPDATE @ModifiedRawData SET [Process] = [Process] - 1
FROM @ModifiedRawData
WHERE [Process] &gt; 0

--&gt; Now look ahead and get (temporarily store) the replaced &quot;Reference&quot; information (chained UPDATEs)
-- this is required for UPDATEs occurring before the most recent UPDATE
UPDATE #T1 SET --&gt; redundant the for the first level
[RowLog Contents 0_var] = A.[RowLog Contents 0_var]
,[RowLog Contents 0] = A.[RowLog Contents 0]
FROM @ModifiedRawData A
INNER JOIN #T1 B ON B.[NextEntry] = A.[EntryID]
WHERE B.[Process] = 1
--select * from #T1
END
END
END
------------------------------------------------------------------------------------------------------------
IF (@Debug &gt; 0) BEGIN
print 'select * from @ModifiedRawData2'
select '@ModifiedRawData2'
select * from @ModifiedRawData ORDER BY [Current LSN], [Datum] Desc
END

DECLARE @RowLogContents VARBINARY(8000)
DECLARE @AllocUnitName NVARCHAR(MAX)
DECLARE @SQL NVARCHAR(MAX)

DECLARE @bitTable TABLE(
[ID] INT
,[Bitvalue] INT
)
--Create table to set the bit position of one byte.

INSERT INTO @bitTable
SELECT 0,2 UNION ALL
SELECT 1,2 UNION ALL
SELECT 2,4 UNION ALL
SELECT 3,8 UNION ALL
SELECT 4,16 UNION ALL
SELECT 5,32 UNION ALL
SELECT 6,64 UNION ALL
SELECT 7,128

--Create table to collect the row data.
DECLARE @DeletedRecords TABLE(
[ID] INT IDENTITY(1,1)
,[RowLogContents] VARBINARY(8000)
,[AllocUnitID] BIGINT
,[TransactionID] NVARCHAR(Max)
,[Slot ID] INT
,[FixedLengthData] SMALLINT
,[TotalNoOfCols] SMALLINT
,[NullBitMapLength] SMALLINT
,[NullBytes] VARBINARY(8000)
,[TotalNoOfVarCols] SMALLINT
,[ColumnOffsetArray] VARBINARY(8000)
,[VarColumnStart] SMALLINT
,[NullBitMap] VARCHAR(MAX)
,[TRANSACTION ID] NVARCHAR(Max)
,[Transaction Name] NVARCHAR(MAX)
,[Datum] VARCHAR(20)
,[Current LSN] VARCHAR(MAX)
,[Begin Time] DATETIME
)
--Create a common table expression to get all the row data plus how many bytes we have for each row.
;WITH RowData AS
(SELECT
[RowLog Contents 0] AS [RowLogContents]
,[AllocUnitID]
,[ID] AS [TransactionID]
,[Slot ID] as [Slot ID]
--[Fixed Length Data] = Substring (RowLog content 0, Status Bit A+ Status Bit B + 1,2 bytes)
,CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) AS [FixedLengthData] --@FixedLengthData

--[TotalnoOfCols] = Substring (RowLog content 0, [Fixed Length Data] + 1,2 bytes)
,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)))) as [TotalNoOfCols]

--[NullBitMapLength]=ceiling([Total No of Columns] /8.0)
,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)) as [NullBitMapLength]

--[Null Bytes] = Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [NullBitMapLength] )
,SUBSTRING([RowLog Contents 0], CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3,
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))) as [NullBytes]

--[TotalNoOfVarCols] = Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 )
,(CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70)
THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3 +
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)), 2))))
ELSE null END) AS [TotalNoOfVarCols]

--[ColumnOffsetArray]= Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 , [TotalNoOfVarCols]*2 )
,(CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70)
THEN SUBSTRING([RowLog Contents 0],
CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3 +
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)) + 2,
(CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70)
THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3 +
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)), 2))))
ELSE null END) * 2)
ELSE null END) AS [ColumnOffsetArray]

-- Variable column Start = Status Bit A+ Status Bit B + [Fixed Length Data] + [Null Bitmap length] + 2+([TotalNoOfVarCols]*2)
,CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1)In (0x30,0x70)
THEN (CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 4 +
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)) +
((CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) In (0x30,0x70)
THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0], 2 + 1, 2)))) + 3 +
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)), 2))))
ELSE null END) * 2))
ELSE NULL END AS [VarColumnStart]
,[TRANSACTION ID]
,[Transaction Name]
,[Datum]
,[Current LSN]
,[Begin Time]
FROM @ModifiedRawData
)

---Use this technique to repeat the row till the no of bytes of the row.
,N1 (n) AS (SELECT 1 UNION ALL SELECT 1)
,N2 (n) AS (SELECT 1 FROM N1 AS X, N1 AS Y)
,N3 (n) AS (SELECT 1 FROM N2 AS X, N2 AS Y)
,N4 (n) AS (SELECT ROW_NUMBER() OVER(ORDER BY X.n) FROM N3 AS X, N3 AS Y)

INSERT INTO @DeletedRecords
SELECT [RowLogContents]
,[AllocUnitID]
,[TransactionID]
,[Slot ID]
,[FixedLengthData]
,[TotalNoOfCols]
,[NullBitMapLength]
,[NullBytes]
,[TotalNoOfVarCols]
,[ColumnOffsetArray]
,[VarColumnStart]
--Get the Null value against each column (1 means null zero means not null)
,[NullBitMap]=(REPLACE(STUFF(
(SELECT ',' +
(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]
FROM N4 AS Nums
JOIN RowData AS C ON n &lt;= NullBitMapLength
CROSS JOIN @bitTable WHERE C.[RowLogContents] = D.[RowLogContents]
ORDER BY [RowLogContents],n ASC FOR XML PATH('')
),1,1,''),',',''))
,[TRANSACTION ID]
,[Transaction Name]
,[Datum]
,[Current LSN]
,[Begin Time]
FROM RowData D
IF (@Debug &gt; 0) BEGIN
print 'select * from @DeletedRecords'
select '@DeletedRecords'
select * from @DeletedRecords
END

CREATE TABLE [#temp_Data](
[FieldName] VARCHAR(MAX) COLLATE database_default NOT NULL
,[FieldValue] VARCHAR(MAX) COLLATE database_default NULL
,[FieldSetValue] VARCHAR(MAX) COLLATE database_default NULL
,[Rowlogcontents] VARBINARY(8000)
,[TransactionID] VARCHAR(MAX) COLLATE database_default NOT NULL
,[Slot ID] INT
,[NonID] INT
,[nullbit] INT
,[TRANSACTION ID] NVARCHAR(MAX)
,[Transaction Name] NVARCHAR(MAX)
,[Datum] VARCHAR(20)
,[Current LSN] VARCHAR(MAX)
,[Begin Time] DATETIME
--,[System_type_id] INT
)
--Create common table expression and join it with the rowdata table
--to get each column details
;With CTE AS
/*This part is for variable data columns*/
(SELECT
A.[ID]
,[Rowlogcontents]
,[TransactionID]
,[Slot ID]
,[TRANSACTION ID]
,[Transaction Name]
,[Datum]
,[Current LSN]
,[Begin Time]
,[NAME]
,cols.leaf_null_bit AS nullbit
,leaf_offset
,ISNULL(syscolumns.length, cols.max_length) AS [length]
,cols.system_type_id
,cols.leaf_bit_position AS bitpos
,ISNULL(syscolumns.xprec, cols.precision) AS xprec
,ISNULL(syscolumns.xscale, cols.scale) AS xscale
,SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null
--Calculate the variable column size from the variable column offset array
,(CASE WHEN leaf_offset&lt;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
) AS [Column value Size]

--Calculate the column length
,(CASE WHEN leaf_offset&lt;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))))
- ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING([ColumnOffsetArray], (2*((leaf_offset*-1)-1))-1, 2)))), 0), [varColumnStart]) --&gt; If Null OR zero
ELSE 0 END
) AS [Column Length]

--Get the Hexa decimal value from the RowlogContent
--HexValue of the variable column=Substring([Column value Size] - [Column Length] + 1,[Column Length])
--This is the data of your column but in the Hexvalue
,CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=1
THEN NULL
ELSE SUBSTRING(Rowlogcontents,
(
(CASE WHEN leaf_offset&lt;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
)
-(
(CASE WHEN leaf_offset&lt;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))))
- ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray], (2*((leaf_offset*-1)-1))-1, 2)))), 0), [varColumnStart]) --&gt; If Null OR zero
ELSE 0 END
))
)+1,(
(CASE WHEN leaf_offset&lt;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))))
- ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray], (2*((leaf_offset*-1)-1))-1, 2)))), 0), [varColumnStart]) --&gt; If Null OR zero
ELSE 0 END
))
)
END AS hex_Value

FROM @DeletedRecords A
INNER JOIN sys.allocation_units allocunits ON
A.[AllocUnitId]=allocunits.[Allocation_Unit_Id]
INNER JOIN sys.partitions partitions ON
(allocunits.type IN (1, 3) AND partitions.hobt_id = allocunits.container_id)
OR (allocunits.type = 2 AND partitions.partition_id = allocunits.container_id)
INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id AND syscolumns.colid = cols.partition_column_id
WHERE leaf_offset &lt; 0

UNION
/*This part is for fixed data columns*/
SELECT
A.[ID]
,[Rowlogcontents]
,[TransactionID]
,[Slot ID]
,[TRANSACTION ID]
,[Transaction Name]
,[Datum]
,[Current LSN]
,[Begin Time]
,[NAME]
,cols.leaf_null_bit AS [nullbit]
,leaf_offset
,ISNULL(syscolumns.length, cols.max_length) AS [length]
,cols.system_type_id
,cols.leaf_bit_position AS bitpos
,ISNULL(syscolumns.xprec, cols.precision) AS xprec
,ISNULL(syscolumns.xscale, cols.scale) AS xscale
,SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null
,(SELECT TOP 1 ISNULL(SUM(CASE WHEN C.leaf_offset &gt;1 THEN max_length ELSE 0 END),0)
FROM sys.system_internals_partition_columns C
WHERE cols.partition_id=C.partition_id AND C.leaf_null_bit&lt;cols.leaf_null_bit
)+5 AS [Column value Size]
,syscolumns.length AS [Column Length]

,CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=1
THEN NULL
ELSE SUBSTRING(Rowlogcontents,
(SELECT TOP 1 ISNULL(SUM(CASE WHEN C.leaf_offset &gt;1 THEN max_length ELSE 0 END),0)
FROM sys.system_internals_partition_columns C
WHERE cols.partition_id =C.partition_id AND C.leaf_null_bit&lt;cols.leaf_null_bit
)+5
,syscolumns.length)
END AS hex_Value
FROM @DeletedRecords A
INNER JOIN sys.allocation_units allocunits ON
A.[AllocUnitId]=allocunits.[Allocation_Unit_Id]
INNER JOIN sys.partitions partitions ON
(allocunits.type IN (1, 3) AND partitions.hobt_id = allocunits.container_id)
OR (allocunits.type = 2 AND partitions.partition_id = allocunits.container_id)
INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id AND syscolumns.colid = cols.partition_column_id
WHERE leaf_offset&gt;0
)
--Converting data from Hexvalue to its orgional datatype.
--Implemented datatype conversion mechanism for each datatype
--Select * from sys.columns Where [object_id]=object_id('' + @SchemaName_n_TableName + '')
--Select * from CTE
INSERT INTO #temp_Data
SELECT
NAME,
CASE
WHEN system_type_id IN (231, 239) THEN LTRIM(RTRIM(CONVERT(NVARCHAR(max),hex_Value))) --NVARCHAR ,NCHAR
WHEN system_type_id IN (167,175) THEN LTRIM(RTRIM(CONVERT(VARCHAR(max),REPLACE(hex_Value, 0x00, 0x20)))) --VARCHAR,CHAR
WHEN system_type_id = 48 THEN CONVERT(VARCHAR(MAX), CONVERT(TINYINT, CONVERT(BINARY(1), REVERSE (hex_Value)))) --TINY INTEGER
WHEN system_type_id = 52 THEN CONVERT(VARCHAR(MAX), CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (hex_Value)))) --SMALL INTEGER
WHEN system_type_id = 56 THEN CONVERT(VARCHAR(MAX), CONVERT(INT, CONVERT(BINARY(4), REVERSE(hex_Value)))) -- INTEGER
WHEN system_type_id = 127 THEN CONVERT(VARCHAR(MAX), CONVERT(BIGINT, CONVERT(BINARY(8), REVERSE(hex_Value))))-- BIG INTEGER
WHEN system_type_id = 61 Then CONVERT(VARCHAR(MAX),CONVERT(DATETIME,CONVERT(VARBINARY(8000),REVERSE (hex_Value))),100) --DATETIME
--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
WHEN system_type_id =58 Then CONVERT(VARCHAR(MAX),CONVERT(SMALLDATETIME,CONVERT(VARBINARY(8000),REVERSE(hex_Value))),100) --SMALL DATETIME
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
WHEN system_type_id In(60,122) THEN CONVERT(VARCHAR(MAX),Convert(MONEY,Convert(VARBINARY(8000),Reverse(hex_Value))),2) --MONEY,SMALLMONEY
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
WHEN system_type_id = 104 THEN CONVERT(VARCHAR(MAX),CONVERT (BIT,CONVERT(BINARY(1), hex_Value)%2)) -- BIT
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) &amp; 0x000FFFFFFFFFFFFF) * POWER(CAST(2 AS FLOAT), -52)) * POWER(CAST(2 AS FLOAT),((CAST(CONVERT(VARBINARY(8000),Reverse(hex_Value)) AS BIGINT) &amp; 0x7ff0000000000000) / EXP(52 * LOG(2))-1023))),53,LEN(hex_Value)))) --- FLOAT
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) &amp; 0x007FFFFF) * POWER(CAST(2 AS Real), -23)) * POWER(CAST(2 AS Real),(((CAST(CONVERT(VARBINARY(8000),Reverse(hex_Value)) AS INT) )&amp; 0x7f800000)/ EXP(23 * LOG(2))-127))AS REAL),23,23)),8) --Real
WHEN system_type_id In (165,173) THEN (CASE WHEN CHARINDEX(0x,cast('' AS XML).value('xs:hexBinary(sql:column(&quot;hex_Value&quot;))', 'VARBINARY(8000)')) = 0 THEN '0x' ELSE '' END) +cast('' AS XML).value('xs:hexBinary(sql:column(&quot;hex_Value&quot;))', 'varchar(max)') -- BINARY,VARBINARY
WHEN system_type_id =36 THEN CONVERT(VARCHAR(MAX),CONVERT(UNIQUEIDENTIFIER,hex_Value)) --UNIQUEIDENTIFIER
END AS [FieldValue]
,NULL AS [FieldSetValue]
,[Rowlogcontents]
,[TransactionID]
,[Slot ID]
,[ID]
,[nullbit]
,[TRANSACTION ID]
,[Transaction Name]
,[Datum]
,[Current LSN]
,[Begin Time]
FROM CTE
ORDER BY [nullbit]

UPDATE #temp_Data SET [FieldSetValue] = --&gt; New Column data
(CASE
-- VARCHAR; CHAR;
WHEN system_type_id In (167,175,189) THEN ISNULL('''' + [FieldValue] + '''','NULL')
-- NVARCHAR; NCHAR
WHEN system_type_id In (231,239) THEN ISNULL('N''' + [FieldValue] + '''','NULL')
-- SMALLDATETIME; DATE; DATETIME; UNIQUEIDENTIFIER
WHEN system_type_id In (58,40,61,36) THEN ISNULL('''' + [FieldValue] + '''','NULL')
-- TINYINT; SMALLINT; INT; REAL; MONEY; FLOAT; BIT; DECIMAL; NUMERIC; SMALLMONEY; BIGINT
WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN ISNULL([FieldValue],'NULL')
END)
FROM sys.columns [D]
WHERE [object_id] = object_id(''+@SchemaName_n_TableName+'')
AND [Fieldname] = D.[name]

/*Create Update statement*/
/*Now we have the modified and actual data as well*/
/*We need to create the update statement in case of recovery*/

;With CTE AS
(SELECT
QUOTENAME([Name]) + '=' + ISNULL([A].[FieldSetValue],'NULL') + ' , ' AS [Field]
----(CASE
---- -- VARCHAR; CHAR;
---- WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' ,' + ' '
---- -- NVARCHAR; NCHAR
---- WHEN system_type_id In (231,239) THEN QUOTENAME([Name]) + '=' + ISNULL('N''' + [A].[FieldValue] + '''','NULL') + ' ,' + ''
---- -- SMALLDATETIME; DATE; DATETIME; UNIQUEIDENTIFIER
---- WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' ,' + ' '
---- -- TINYINT; SMALLINT; INT; REAL; MONEY; FLOAT; BIT; DECIMAL; NUMERIC; SMALLMONEY; BIGINT
---- WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '=' + ISNULL([A].[FieldValue],'NULL') + ' ,' + ' '
---- END) AS [Field]
,A.[Slot ID]
,A.[TransactionID] AS [TransactionID]
,'D' AS [Type] --&gt; Different
,[A].[Rowlogcontents]
,[A].[NonID]
,[A].[nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM #temp_Data AS [A]
INNER JOIN #temp_Data AS [B] ON
[A].[FieldName]=[B].[FieldName]
AND [A].[Slot ID]=[B].[Slot ID]
--And [A].[TransactionID]=[B].[TransactionID]+1
AND [A].[TRANSACTION ID]=[B].[TRANSACTION ID] --&gt; new condition
AND [A].[TransactionID]&lt;&gt;[B].[TransactionID] --&gt; new condition
----AND [B].[TransactionID] =
---- (SELECT Min(Cast([TransactionID] AS INT)) AS [TransactionID]
---- FROM #temp_Data AS [C]
---- WHERE [A].[Slot ID]=[C].[Slot ID]
---- GROUP BY [Slot ID]
---- )
INNER JOIN sys.columns [D] ON [object_id] = object_id(''+@SchemaName_n_TableName+'')
AND A.[Fieldname] = D.[name]
WHERE ISNULL([A].[FieldValue],'') &lt;&gt; ISNULL([B].[FieldValue],'')

UNION ALL

SELECT
QUOTENAME([Name]) + '=' + ISNULL([A].[FieldSetValue],'NULL') + ' AND' AS [Field]
----(CASE
---- WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (231,239) THEN QUOTENAME([Name]) + '='+ ISNULL('N''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '=' + ISNULL([A].[FieldValue],'NULL') + ' AND'
---- END) AS [Field]
,A.[Slot ID]
,A.[TransactionID] AS [TransactionID]
,'S' AS [Type] --&gt; Same
,[A].[Rowlogcontents]
,[A].[NonID]
,[A].[nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM #temp_Data AS [A]
INNER JOIN #temp_Data AS [B] ON [A].[FieldName] = [B].[FieldName]
AND [A].[Slot ID] = [B].[Slot ID]
--AND [A].[TransactionID] = [B].[TransactionID]+1
AND [A].[TRANSACTION ID]=[B].[TRANSACTION ID] --&gt; new condition
AND [A].[TransactionID]&lt;&gt;[B].[TransactionID] --&gt; new condition
----AND [B].[TransactionID] =
---- (SELECT MIN(CAST([TransactionID] AS INT)) AS [TransactionID]
---- FROM #temp_Data AS [C]
---- WHERE [A].[Slot ID]=[C].[Slot ID]
---- GROUP BY [Slot ID]
---- )
INNER JOIN sys.columns [D] ON [object_id] = object_id('' + @SchemaName_n_TableName + '')
AND [A].[Fieldname] = D.[name]
WHERE ISNULL([A].[FieldValue],'') = ISNULL([B].[FieldValue],'')
----AND A.[TransactionID] NOT IN
---- (SELECT MIN(CAST([TransactionID] AS INT)) AS [TransactionID]
---- FROM #temp_Data AS [C]
---- WHERE [A].[Slot ID]=[C].[Slot ID]
---- GROUP BY [Slot ID]
---- )

UNION ALL --&gt; new

SELECT --&gt; new
QUOTENAME([Name]) + '=' + ISNULL([A].[FieldSetValue],'NULL') + ' AND' AS [Field]
----(CASE
---- WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (231,239) THEN QUOTENAME([Name]) + '=' + ISNULL('N''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '=' + ISNULL([A].[FieldValue],'NULL') + ' AND'
---- END) AS [Field]
,A.[Slot ID]
,A.[TransactionID] AS [TransactionID]
,CASE WHEN ([A].[Transaction Name] = 'INSERT') THEN 'N' ELSE 'O' END AS [Type] --&gt; New or Old
,[A].[Rowlogcontents]
,[A].[NonID]
,[A].[nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM #temp_Data AS [A]
INNER JOIN #temp_Data AS [B] ON
[A].[FieldName]=[B].[FieldName]
AND [A].[Slot ID]=[B].[Slot ID]
AND [A].[Transaction Name] IN ('INSERT','DELETE')
AND [A].[TransactionID]=[B].[TransactionID] --&gt; self
INNER JOIN sys.columns [D] ON [object_id] = object_id(''+@SchemaName_n_TableName+'')
AND A.[Fieldname] = D.[name]
)

,CTEUpdateQuery AS
(SELECT
CASE A.[Transaction Name]
WHEN 'UPDATE' THEN
'UPDATE [' + @SchemaName_n_TableName + '] SET ' +
LEFT(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM CTE B
WHERE A.[Slot ID]=B.[Slot ID] AND A.[TransactionID]=B.[TransactionID] AND B.[Type]='D' FOR XML PATH('')
),1,1,''), LEN(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM CTE B
WHERE A.[Slot ID]=B.[Slot ID] AND A.[TransactionID]=B.[TransactionID] AND B.[Type]='D' FOR XML PATH('')
),1,1,'') )-2 --&gt; -2 removes the final ', '
) +

' WHERE ' +

LEFT(STUFF(
(SELECT ' ' + ISNULL([Field],'') + ' '
FROM CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='S' FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' ' + ISNULL([Field],'') + ' '
FROM CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='S' FOR XML PATH('')
),1,1,'') )-4 --&gt; -4 removes the final ' AND'
)
WHEN 'DELETE' THEN
'INSERT INTO [' + @SchemaName_n_TableName + '] (' +
LEFT(STUFF(
(SELECT ' [' + ISNULL([FieldName] + '],','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' [' + ISNULL([FieldName] + '],','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') )-1 --&gt; -1 removes the final ','
) + ') VALUES (' +
LEFT(STUFF(
(SELECT ' ' + ISNULL([FieldSetValue] + ',','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' ' + ISNULL([FieldSetValue] + ',','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') )-1 --&gt; -1 removes the final ','
) + ')'
WHEN 'INSERT' THEN
'DELETE FROM [' + @SchemaName_n_TableName + '] WHERE ' +
LEFT(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='N' FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='N' FOR XML PATH('')
),1,1,'') )-4 --&gt; -4 removes the final ' AND'
)
END AS [Update Statement]
,[Slot ID]
,[TransactionID]
,[Rowlogcontents]
,[A].[NonID]
,MAX([A].[nullbit])+1 AS [nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM CTE A
GROUP BY [Slot ID]
,[TransactionID]
,[Rowlogcontents]
,[A].[NonID]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Begin Time]
,[A].[Current LSN]
,[A].[Datum]
)

INSERT INTO #temp_Data
SELECT 'Update Statement',ISNULL([Update Statement],''),NULL,NULL,[TransactionID],[Slot ID],[NonID],[nullbit],[TRANSACTION ID],[Transaction Name],[Datum],[Current LSN],[Begin Time]
FROM CTEUpdateQuery

/****************************************/
IF (@Debug &gt; 0) BEGIN
print 'select * from CTE1'
select 'CTE1'
select * from (
/*This part is for variable data columns*/
SELECT
A.[ID]
,[Rowlogcontents]
,[TransactionID]
,[Slot ID]
,[TRANSACTION ID]
,[Transaction Name]
,[Datum]
,[Current LSN]
,[Begin Time]
,[NAME]
,cols.leaf_null_bit AS nullbit
,leaf_offset
,ISNULL(syscolumns.length, cols.max_length) AS [length]
,cols.system_type_id
,cols.leaf_bit_position AS bitpos
,ISNULL(syscolumns.xprec, cols.precision) AS xprec
,ISNULL(syscolumns.xscale, cols.scale) AS xscale
,SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null
--Calculate the variable column size from the variable column offset array
,(CASE WHEN leaf_offset&lt;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
) AS [Column value Size]

--Calculate the column length
,(CASE WHEN leaf_offset&lt;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))))
- ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (SUBSTRING([ColumnOffsetArray], (2*((leaf_offset*-1)-1))-1, 2)))), 0), [varColumnStart]) --&gt; If Null OR zero
ELSE 0 END
) AS [Column Length]

--Get the Hexa decimal value from the RowlogContent
--HexValue of the variable column=Substring([Column value Size] - [Column Length] + 1,[Column Length])
--This is the data of your column but in the Hexvalue
,CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=1
THEN NULL
ELSE SUBSTRING(Rowlogcontents,
(
(CASE WHEN leaf_offset&lt;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
)
-(
(CASE WHEN leaf_offset&lt;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))))
- ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray], (2*((leaf_offset*-1)-1))-1, 2)))), 0), [varColumnStart]) --&gt; If Null OR zero
ELSE 0 END
))
)+1,(
(CASE WHEN leaf_offset&lt;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))))
- ISNULL(NULLIF(CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray], (2*((leaf_offset*-1)-1))-1, 2)))), 0), [varColumnStart]) --&gt; If Null OR zero
ELSE 0 END
))
)
END AS hex_Value

FROM @DeletedRecords A
INNER JOIN sys.allocation_units allocunits ON
A.[AllocUnitId]=allocunits.[Allocation_Unit_Id]
INNER JOIN sys.partitions partitions ON
(allocunits.type IN (1, 3) AND partitions.hobt_id = allocunits.container_id)
OR (allocunits.type = 2 AND partitions.partition_id = allocunits.container_id)
INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id AND syscolumns.colid = cols.partition_column_id
WHERE leaf_offset &lt; 0

UNION
/*This part is for fixed data columns*/
SELECT
A.[ID]
,[Rowlogcontents]
,[TransactionID]
,[Slot ID]
,[TRANSACTION ID]
,[Transaction Name]
,[Datum]
,[Current LSN]
,[Begin Time]
,[NAME]
,cols.leaf_null_bit AS [nullbit]
,leaf_offset
,ISNULL(syscolumns.length, cols.max_length) AS [length]
,cols.system_type_id
,cols.leaf_bit_position AS bitpos
,ISNULL(syscolumns.xprec, cols.precision) AS xprec
,ISNULL(syscolumns.xscale, cols.scale) AS xscale
,SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null
,(SELECT TOP 1 ISNULL(SUM(CASE WHEN C.leaf_offset &gt;1 THEN max_length ELSE 0 END),0)
FROM sys.system_internals_partition_columns C
WHERE cols.partition_id=C.partition_id AND C.leaf_null_bit&lt;cols.leaf_null_bit
)+5 AS [Column value Size]
,syscolumns.length AS [Column Length]

,CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1)=1
THEN NULL
ELSE SUBSTRING(Rowlogcontents,
(SELECT TOP 1 ISNULL(SUM(CASE WHEN C.leaf_offset &gt;1 THEN max_length ELSE 0 END),0)
FROM sys.system_internals_partition_columns C
WHERE cols.partition_id =C.partition_id AND C.leaf_null_bit&lt;cols.leaf_null_bit
)+5
,syscolumns.length)
END AS hex_Value
FROM @DeletedRecords A
INNER JOIN sys.allocation_units allocunits ON A.[AllocUnitId]=allocunits.[Allocation_Unit_Id]
INNER JOIN sys.partitions partitions ON
(allocunits.type IN (1, 3) AND partitions.hobt_id = allocunits.container_id)
OR (allocunits.type = 2 AND partitions.partition_id = allocunits.container_id)
INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id AND syscolumns.colid = cols.partition_column_id
WHERE leaf_offset&gt;0
) AS t1
order by [Begin Time],[ID],[nullbit]

print 'select * from CTE2'
select 'CTE2'
select * into #CTE from (
SELECT
QUOTENAME([Name]) + '=' + ISNULL([A].[FieldSetValue],'NULL') + ' , ' AS [Field]
----(CASE
---- -- VARCHAR; CHAR;
---- WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' ,' + ' '
---- -- NVARCHAR; NCHAR
---- WHEN system_type_id In (231,239) THEN QUOTENAME([Name]) + '=' + ISNULL('N''' + [A].[FieldValue] + '''','NULL') + ' ,' + ''
---- -- SMALLDATETIME; DATE; DATETIME; UNIQUEIDENTIFIER
---- WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' ,' + ' '
---- -- TINYINT; SMALLINT; INT; REAL; MONEY; FLOAT; BIT; DECIMAL; NUMERIC; SMALLMONEY; BIGINT
---- WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '=' + ISNULL([A].[FieldValue],'NULL') + ' ,' + ' '
---- END) AS [Field]
,A.[Slot ID]
,A.[TransactionID] AS [TransactionID]
,'D' AS [Type] --&gt; Different
,[A].[Rowlogcontents]
,[A].[NonID]
,[A].[nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM #temp_Data AS [A]
INNER JOIN #temp_Data AS [B] ON
[A].[FieldName]=[B].[FieldName]
AND [A].[Slot ID]=[B].[Slot ID]
--And [A].[TransactionID]=[B].[TransactionID]+1
AND [A].[TRANSACTION ID]=[B].[TRANSACTION ID] --&gt; new condition
AND [A].[TransactionID]&lt;&gt;[B].[TransactionID] --&gt; new condition
AND [A].[Current LSN]=[B].[Current LSN] --&gt; new condition
----AND [B].[TransactionID] =
---- (SELECT Min(Cast([TransactionID] AS INT)) AS [TransactionID]
---- FROM #temp_Data AS [C]
---- WHERE [A].[Slot ID]=[C].[Slot ID]
---- GROUP BY [Slot ID]
---- )
INNER JOIN sys.columns [D] ON [object_id] = object_id(''+@SchemaName_n_TableName+'')
AND A.[Fieldname] = D.[name]
WHERE ISNULL([A].[FieldValue],'') &lt;&gt; ISNULL([B].[FieldValue],'')

UNION ALL

SELECT
QUOTENAME([Name]) + '=' + ISNULL([A].[FieldSetValue],'NULL') + ' AND' AS [Field]
----(CASE
---- WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (231,239) THEN QUOTENAME([Name]) + '='+ ISNULL('N''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '=' + ISNULL([A].[FieldValue],'NULL') + ' AND'
---- END) AS [Field]
,A.[Slot ID]
,A.[TransactionID] AS [TransactionID]
,'S' AS [Type] --&gt; Same
,[A].[Rowlogcontents]
,[A].[NonID]
,[A].[nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM #temp_Data AS [A]
INNER JOIN #temp_Data AS [B] ON [A].[FieldName] = [B].[FieldName]
AND [A].[Slot ID] = [B].[Slot ID]
--AND [A].[TransactionID] = [B].[TransactionID]+1
AND [A].[TRANSACTION ID]=[B].[TRANSACTION ID] --&gt; new condition
AND [A].[TransactionID]&lt;&gt;[B].[TransactionID] --&gt; new condition
AND [A].[Current LSN]=[B].[Current LSN] --&gt; new condition
----AND [B].[TransactionID] =
---- (SELECT MIN(CAST([TransactionID] AS INT)) AS [TransactionID]
---- FROM #temp_Data AS [C]
---- WHERE [A].[Slot ID]=[C].[Slot ID]
---- GROUP BY [Slot ID]
---- )
INNER JOIN sys.columns [D] ON [object_id] = object_id('' + @SchemaName_n_TableName + '')
AND [A].[Fieldname] = D.[name]
WHERE ISNULL([A].[FieldValue],'') = ISNULL([B].[FieldValue],'')
----AND A.[TransactionID] NOT IN
---- (SELECT MIN(CAST([TransactionID] AS INT)) AS [TransactionID]
---- FROM #temp_Data AS [C]
---- WHERE [A].[Slot ID]=[C].[Slot ID]
---- GROUP BY [Slot ID]
---- )

UNION ALL --&gt; new

SELECT --&gt; new
QUOTENAME([Name]) + '=' + ISNULL([A].[FieldSetValue],'NULL') + ' AND' AS [Field]
----(CASE
---- WHEN system_type_id In (167,175,189) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (231,239) THEN QUOTENAME([Name]) + '=' + ISNULL('N''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (58,40,61,36) THEN QUOTENAME([Name]) + '=' + ISNULL('''' + [A].[FieldValue] + '''','NULL') + ' AND'
---- WHEN system_type_id In (48,52,56,59,60,62,104,106,108,122,127) THEN QUOTENAME([Name]) + '=' + ISNULL([A].[FieldValue],'NULL') + ' AND'
---- END) AS [Field]
,A.[Slot ID]
,A.[TransactionID] AS [TransactionID]
,CASE WHEN ([A].[Transaction Name] = 'INSERT') THEN 'N' ELSE 'O' END AS [Type] --&gt; New or Old
,[A].[Rowlogcontents]
,[A].[NonID]
,[A].[nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM #temp_Data AS [A]
INNER JOIN #temp_Data AS [B] ON
[A].[FieldName]=[B].[FieldName]
AND [A].[Slot ID]=[B].[Slot ID]
AND [A].[Transaction Name] IN ('INSERT','DELETE')
AND [A].[TransactionID]=[B].[TransactionID] --&gt; self
INNER JOIN sys.columns [D] ON [object_id] = object_id(''+@SchemaName_n_TableName+'')
AND A.[Fieldname] = D.[name]
) as t2
select * from #CTE
order by [Begin Time],[Slot ID],[NonID],[nullbit]

print 'select * from CTEUpdateQuery'
select 'CTEUpdateQuery'
SELECT
CASE A.[Transaction Name]
WHEN 'UPDATE' THEN
'UPDATE [' + @SchemaName_n_TableName + '] SET ' +
LEFT(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM #CTE B
WHERE A.[Slot ID]=B.[Slot ID] AND A.[TransactionID]=B.[TransactionID] AND B.[Type]='D' FOR XML PATH('')
),1,1,''), LEN(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM #CTE B
WHERE A.[Slot ID]=B.[Slot ID] AND A.[TransactionID]=B.[TransactionID] AND B.[Type]='D' FOR XML PATH('')
),1,1,'') )-2 --&gt; -2 removes the final ', '
) +

' WHERE ' +

LEFT(STUFF(
(SELECT ' ' + ISNULL([Field],'') + ' '
FROM #CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='S' FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' ' + ISNULL([Field],'') + ' '
FROM #CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='S' FOR XML PATH('')
),1,1,'') )-4 --&gt; -4 removes the final ' AND'
)
WHEN 'DELETE' THEN
'INSERT INTO [' + @SchemaName_n_TableName + '] (' +
LEFT(STUFF(
(SELECT ' [' + ISNULL([FieldName] + '],','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' [' + ISNULL([FieldName] + '],','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') )-1 --&gt; -1 removes the final ','
) + ') VALUES (' +
LEFT(STUFF(
(SELECT ' ' + ISNULL([FieldSetValue] + ',','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' ' + ISNULL([FieldSetValue] + ',','')
FROM #temp_Data C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Rowlogcontents] IS NOT NULL FOR XML PATH('')
),1,1,'') )-1 --&gt; -1 removes the final ','
) + ')'
WHEN 'INSERT' THEN
'DELETE FROM [' + @SchemaName_n_TableName + '] WHERE ' +
LEFT(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM #CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='N' FOR XML PATH('')
),1,1,'') ,LEN(STUFF(
(SELECT ' ' + ISNULL([Field],'')
FROM #CTE C
WHERE A.[Slot ID]=C.[Slot ID] AND A.[TransactionID]=C.[TransactionID] AND C.[Type]='N' FOR XML PATH('')
),1,1,'') )-4 --&gt; -4 removes the final ' AND'
)
END AS [Update Statement]
,[Slot ID]
,[TransactionID]
,[Rowlogcontents]
,[A].[NonID]
,MAX([A].[nullbit])+1 AS [nullbit]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Datum]
,[A].[Current LSN]
,[A].[Begin Time]
FROM #CTE A
GROUP BY [Slot ID]
,[TransactionID]
,[Rowlogcontents]
,[A].[NonID]
,[A].[TRANSACTION ID]
,[A].[Transaction Name]
,[A].[Begin Time]
,[A].[Current LSN]
,[A].[Datum]
drop table #CTE
END

IF (@Debug &gt; 0) BEGIN
print 'select * from temp_Data1'
select '#temp_Data1'
select * from #temp_Data order by [TransactionID],[nullbit]
END

--Create the column name in the same order to do pivot table.
DECLARE @FieldNames VARCHAR(MAX)
SET @FieldNames = STUFF(
(SELECT ','+CAST(QUOTENAME([Name]) AS VARCHAR(MAX))
FROM syscolumns
WHERE id = object_id(''+@SchemaName_n_TableName+'') FOR XML PATH('')
),1,1,'')

--Finally, pivot table and get the change history so one can get the data back.
--The [Update Statement] column will give you the query that you can execute for recovery.
-- NOTE: Need a placeholder type for [Execution Account] (else it will be int)
SET @sql = 'SELECT ROW_NUMBER() OVER (ORDER BY [LSN],[Datum] DESC) AS [RowNo],' + @FieldNames
+ ',[Datum],[Update Statement],[Transaction],-1 AS [Prev RowNo],[Date],[Execution Account],[TRANSACTION ID],[Slot ID],[LSN] '
+ 'INTO [@ResultTable] '
+ 'FROM (SELECT [FieldName],[FieldValue],[Transaction Name] AS [Transaction],[Datum] AS [Datum],[Begin Time] AS [Date]'
+',[TRANSACTION ID] AS [Execution Account],[TRANSACTION ID],[Slot ID],[Current LSN] AS [LSN] FROM #temp_Data) AS src '
+ 'PIVOT (Min([FieldValue]) FOR [FieldName] IN (' + @FieldNames + ',[Update Statement])) AS pvt '
+ 'ORDER BY [LSN],[Datum] DESC'

--&gt; Create a table to hold the dynamic SQL results --&gt; doesn't seem to work
DECLARE @sql1 VARCHAR(MAX)
SET @sql1 = 'Declare @result Table ([RecNo] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,' + REPLACE(@FieldNames,',',' varchar(max),') + 'varchar(max),'
+ '[Datum] varchar(20),[Update Statement] varchar(max),[TRANSACTION ID] varchar(max),[Transaction] varchar(max)'
+',[Slot ID] int,[LSN] varchar(max),[Prev RowNo] int,[Date] varchar(20))'
--EXEC(@sql1)

EXEC sp_executesql @sql
IF OBJECT_ID (N'[@ResultTable]', N'U') IS NOT NULL
BEGIN
UPDATE [@ResultTable] SET [Prev RowNo] =
ISNULL((SELECT MAX(B.[RowNo]) AS [ID] FROM [@ResultTable] B
WHERE B.[Slot ID] = A.[Slot ID] AND B.[RowNo] &lt; A.[RowNo]),0)
FROM [@ResultTable] A
UPDATE [@ResultTable] SET [Execution Account] =
(SELECT [Transaction Account] FROM #TransIdAllList B WHERE B.[Transaction ID] = A.[Transaction ID])
FROM [@ResultTable] A
SELECT * FROM [@ResultTable]
DROP TABLE [@ResultTable]
END

IF (@Debug &gt; 0) BEGIN
print @FieldNames
print @sql1
print ''
print @sql
END

GO --&gt; required for testing/debugging (dumps all local entities from above)
SET NOEXEC OFF

PS: Não entendeu nada ?!?? Blz… isso não é para os fracos… 😉

Gerar scripts de bases

Esse script é uma modificação de um script do simple-talk apenas adicionar a opção de função para facilitar a vida

o importante para este script funcionar é:

  1. criar um diretório com o nome Script-DBObjectsIntoFolders dentro do diretório C:\Windows\System32\WindowsPowerShell\v1.0\Modules\
  2. Salvar este script com o nome Script-DBObjectsIntoFolders.psm1
  3. Usando o powershell digitar import-module Script-DBObjectsIntoFolders

 


#https://www.simple-talk.com/sql/database-administration/automated-script-generation-with-powershell-and-smo/

function global:Script-DBObjectsIntoFolders([string]$server, [string]$dbname, [string]$DirectoryToSave){

# set &quot;Option Explicit&quot; to catch subtle errors
set-psdebug -strict
$DirectoryToSaveTo=$DirectoryToSave # local directory to save build-scripts to
$servername=$server # server name and instance
$Database=$dbname # the database to copy from
$ErrorActionPreference = &quot;stop&quot; # you can opt to stagger on, bleeding, if an error occurs
Trap {
# Handle the error
$err = $_.Exception
write-host $err.Message
while( $err.InnerException ) {
$err = $err.InnerException
write-host $err.Message
};
# End the script.
break
}
# Load SMO assembly, and if we're running SQL 2008 DLLs load the SMOExtended and SQLWMIManagement libraries
$v = [System.Reflection.Assembly]::LoadWithPartialName( 'Microsoft.SqlServer.SMO')
if ((($v.FullName.Split(','))[1].Split('='))[1].Split('.')[0] -ne '9') {
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMOExtended') | out-null
}
$My='Microsoft.SqlServer.Management.Smo'
$s = new-object (&quot;$My.Server&quot;) $ServerName # get the server.
$Server=$s.netname -replace '[\\\/\:\.]',' ' # remove characters that can cause problems
$instance = $s.instanceName -replace '[\\\/\:\.]',' ' # ditto
$DatabaseName =$database -replace '[\\\/\:\.]',' ' # ditto
$DirectoryToSaveTo=$DirectoryToSaveTo+$Server+'\'+$Instance+'\' # database scripts are local on client
if (!( Test-Path -path &quot;$DirectoryToSaveTo&quot; )) # create it if not existing
{$progress =&quot;attempting to create directory $DirectoryToSaveTo&quot;
Try { New-Item &quot;$DirectoryToSaveTo&quot; -type directory | out-null }
Catch [system.exception]{
Write-Error &quot;error while $progress. $_&quot;
return
}
}
&lt;# now we will use the canteen system of SMO to specify what we want from the script. It is best to have a list of the defaults to hand and just override the defaults where necessary, but there is a chance that a later revision of SMO could change the defaults, so beware! #&gt;
$CreationScriptOptions = new-object (&quot;$My.ScriptingOptions&quot;)
$CreationScriptOptions.ExtendedProperties= $true # yes, we want these
$CreationScriptOptions.DRIAll= $true # and all the constraints
$CreationScriptOptions.Indexes= $true # Yup, these would be nice
$CreationScriptOptions.Triggers= $true # This should be included when scripting a database
$CreationScriptOptions.ScriptBatchTerminator = $true # this only goes to the file
$CreationScriptOptions.Filename = &quot;$($DirectoryToSaveTo)$($DatabaseName)_Build.sql&quot;;
# we have to write to a file to get the GOs
$CreationScriptOptions.IncludeHeaders = $true; # of course
$CreationScriptOptions.ToFileOnly = $true # no need of string output as well
$CreationScriptOptions.IncludeIfNotExists = $true # not necessary but it means the script can be more versatile
$transfer = new-object (&quot;$My.Transfer&quot;) $s.Databases[$Database]
$transfer.options=$CreationScriptOptions # tell the transfer object of our preferences
$scripter = new-object (&quot;$My.Scripter&quot;) $s # script out the database creation
$scripter.options=$CreationScriptOptions # with the same options
$scripter.Script($s.Databases[$Database]) # do it
&quot;USE $Database&quot; | Out-File -Append -FilePath &quot;$($DirectoryToSaveTo)$($DatabaseName)_Build.sql&quot;
&quot;GO&quot; | Out-File -Append -FilePath &quot;$($DirectoryToSaveTo)$($DatabaseName)_Build.sql&quot;
# add the database object build script
$transfer.options.AppendToFile=$true
$transfer.options.ScriptDrops=$true
$transfer.EnumScriptTransfer()
$transfer.options.ScriptDrops=$false
$transfer.EnumScriptTransfer()
&quot;All written to $($DirectoryToSaveTo)$($DatabaseName)_Build.sql&quot;
}