A few days ago I was working on indexes on a development server. Accidentally I dropped few indexes. Fortunately, since I normally take the database backup, I restored the backup and it recovered all indexes perfectly. What if I didn’t have the backup and I did it on production server, it would be just BOOOOOM ;). However, I looked for a solution on the internet how to recover the dropped index without backup. But could not get the proper solution. So finally I decided to develop a solution which I would be sharing with you.
Note : It is recommended to take database backup on regular basis and recover any disaster from backup.
Lets create & drop one nonclustered index on a table to demonstrate it.
USE AdventureWorks2012 GO CREATE NONCLUSTERED INDEX [IX_PurchaseOrderDetail_Sample] ON [Purchasing].[PurchaseOrderDetail] ( [OrderQty], [ReceivedQty], [RejectedQty], [ProductID] ) GO DROP INDEX [IX_PurchaseOrderDetail_Sample] ON [Purchasing].[PurchaseOrderDetail]
Lets recover it step by step :
Step 1:
In this step, you need to create the given below function in the database from where you need to do the recovery.
USE AdventureWorks2012 GO CREATE FUNCTION [dbo].[UDF_Get_Index_Column_Name] (@Object_id bigint,@Column_id bigint) RETURNS varchar(Max) AS Begin Declare @Column_Name as varchar(Max) Select @Column_Name=[name] from sys.columns where object_id =@Object_id And column_id =@Column_id Return @Column_Name End GO
Step 2:
In this step, you need to create the given below stored procedure in the database from where you need to do the recovery.
CREATE PROCEDURE Recover_Dropped_Index_Proc @Date_From DATETIME='1900/01/01', @Date_To DATETIME ='9999/12/31' AS ;With CTE AS (Select Row_number() Over(Partition By A.[Transaction ID] Order By [Transaction ID]) as [SNO] ,A.[Transaction ID] ,'CREATE ' + (Case When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =0 Then 'Heap' When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =1 Then 'Clustered' When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =2 Then 'Nonclustered' When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =3 Then 'XML' When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =4 Then 'Spatial' When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =6 Then 'Nonclustered columnstore' end) + ' INDEX ' + Quotename( Replace(Substring( A.[RowLog Contents 0],47,LEN(A.[RowLog Contents 0])),0x00,0x)) +' ON ' + Sch.name +'.' + Object_Name( Convert(varbinary,Reverse(Substring(A.[RowLog Contents 0],5,4)))) as [Command1] , [dbo].[UDF_Get_Index_Column_Name] ( Convert(bigint, Convert(varbinary,Reverse(Substring(A.[RowLog Contents 0],5,4)))) , Convert(bigint , Convert(varbinary,Substring(A.[RowLog Contents 0],20,2)))) as [Column Name] , Convert(int,Substring(A.[RowLog Contents 0],22,1)) as [Type] , SUM(1) OVER(Partition By A.[Transaction ID]) as [Total Rows] From sys.fn_dblog(NULL, NULL) As A Inner Join sys.tables As tbl On Object_Name(Convert (varbinary,Reverse(Substring(A.[RowLog Contents 0],5,4))))=tbl.name Inner Join sys.schemas As Sch On tbl.schema_id =sch.schema_id Where A.AllocUnitName In ('sys.sysiscols.clst','sys.sysidxstats.clst') And Context In ('LCX_MARK_AS_GHOST', 'LCX_HEAP') And Operation in ('LOP_DELETE_ROWS') And A.[TRANSACTION ID] In (SELECT DISTINCT [TRANSACTION ID] FROM sys.fn_dblog(NULL, NULL) Where Context IN ('LCX_NULL') And Operation In ('LOP_BEGIN_XACT') And [Transaction Name] Like '%DROP INDEX%' And CONVERT(NVARCHAR(11),[Begin Time]) Between @Date_From And @Date_To) ) Select (Case When [type]=0 And [SNo] =2 And [SNO]<>[Total rows] Then '(' + [Column Name] +',' When [type]=0 And [SNo] =2 And [SNO]=[Total rows] Then '(' + [Column Name] +')' When [type]=0 And [SNo] >2 And [SNO]<>[Total rows] Then [Column Name] + ',' When [type]=0 And [SNo] >2 And [SNO]=[Total rows] Then [Column Name] + ')' else Command1 end) AS [Text] from CTE GO
Step 3 :
In this step, you need to execute the above stored procedure to recover the dropped index. Given below is the syntax.
You must supply the correct recovery dates to the stored procedure to recover proper records.
USE AdventureWorks2012 GO EXEC [Recover_Dropped_Index_Proc] '2013/04/01','9999/12/31' --OUTPUT
Step 4 :
Just copy the above result set and paste in the query window and execute it using correct database and you will get your indexes back in your database.
Reblogged this on Sutoprise Avenue, A SutoCom Source.
[…] couple of days ago, I developed a tool how to recover the dropped index without backup. In this tool, running total needs to be generated. So I did some research and came across couple […]
Hi,
First of all thanks for sharing your script, it works fine until the log backup is not taken. If you write a query to get the details from log backup it would be most helpful (fn_dump_dblog) because most of the production servers will be configured to take log backup in short intervals(may in 15 mins).
Thanks again.
Thanks for your query.
We cannot create Functions\SPs in Production server without Change requests, hence modified the query as Below.
USE Adventuretest
DECLARE @Date_From DATETIME=’2015/07/20′
DECLARE @Date_To DATETIME =’9999/12/31′
;With CTE AS (
Select
Row_number() Over(Partition By A.[Transaction ID]
Order By [Transaction ID]) as [SNO]
,A.[Transaction ID]
,’CREATE ‘ +
(Case When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =0
Then ‘Heap’
When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =1
Then ‘Clustered’
When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =2
Then ‘Nonclustered’
When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =3
Then ‘XML’
When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =4
Then ‘Spatial’
When Convert(int,Substring(A.[RowLog Contents 0],22,1)) =6
Then ‘Nonclustered columnstore’
end)
+ ‘ INDEX ‘ + Quotename(
Replace(Substring(
A.[RowLog Contents 0],47,LEN(A.[RowLog Contents 0])),0x00,0x))
+’ ON ‘ + Sch.name +’.’
+ Object_Name(
Convert(varbinary,Reverse(Substring(A.[RowLog Contents 0],5,4))))
as [Command1],
(COL_NAME((Convert(bigint,Convert(varbinary,Reverse(Substring(A.[RowLog Contents 0],5,4))))),(Convert(bigint,Convert(varbinary,Substring(A.[RowLog Contents 0],20,2)))))) as [Column Name]
, Convert(int,Substring(A.[RowLog Contents 0],22,1)) as [Type]
, SUM(1) OVER(Partition By A.[Transaction ID]) as [Total Rows]
From sys.fn_dblog(NULL, NULL) As A
Inner Join sys.tables As tbl
On Object_Name(Convert
(varbinary,Reverse(Substring(A.[RowLog Contents 0],5,4))))=tbl.name
Inner Join sys.schemas As Sch On tbl.schema_id =sch.schema_id
Where A.AllocUnitName In (‘sys.sysiscols.clst’,’sys.sysidxstats.clst’)
And Context In (‘LCX_MARK_AS_GHOST’, ‘LCX_HEAP’)
And Operation in (‘LOP_DELETE_ROWS’)
And A.[TRANSACTION ID] In (SELECT DISTINCT [TRANSACTION ID]
FROM sys.fn_dblog(NULL, NULL)
Where Context IN (‘LCX_NULL’) And Operation In (‘LOP_BEGIN_XACT’)
And [Transaction Name] Like ‘%DROP INDEX%’
And CONVERT(NVARCHAR(11),[Begin Time]) Between @Date_From And @Date_To)
)
Select (Case When [type]=0 And [SNo] =2 And [SNO][Total rows]
Then ‘(‘ + [Column Name] +’,’
When [type]=0 And [SNo] =2 And [SNO]=[Total rows]
Then ‘(‘ + [Column Name] +’)’
When [type]=0 And [SNo] >2 And [SNO][Total rows]
Then [Column Name] + ‘,’
When [type]=0 And [SNo] >2 And [SNO]=[Total rows]
Then [Column Name] + ‘)’
else Command1 end) AS [Text] from CTE
[…] https://raresql.com/2013/04/24/sql-server-how-to-recover-the-dropped-indexes-without-backup/ […]
Hello,
Your articles are really good. Have you ever tried to recover data/table directly from backup file without restoring it. Do you have any idea how to proceed on this idea, there are many third party tools available for this task but I am looking for something to create as free.
Regards,
Davinder