Blog Post

RYO Maintenance Plan – Enhancement Request

,

In response to my last post, I got a very interesting comment from Philip:

“I would love to see a way to add logic that will verify at least X backup files for a given DB remain. Specifically, in my organization, I would like to delete all but the most recent one– even if that one is three weeks old because I took that particular DB offline or detached for one reason or another. As it is, I must now sort by file name, then delete all but the oldest one manually.”

He raises a good point. Generally speaking, when I take a backup that I know I have to keep, I rename it with a .keep extension so it doesn’t get cleaned up with everything else. Well, that works great when I know that that’s a backup I have to keep. But what about Philip’s example, suppose I take a particular database offline? If I don’t specifically rename that last backup, it will be gone in a week or whatever my retention period happens to be. Or maybe I backup different databases at different intervals (DatabaseX nightly, and DatabaseY weekly) and I’d just like to retain the last 3 backups. Regardless of the reason(s) behind it, I thought it was a worthy enhancement, so I came up with some code that should work.

The first thing I need to do is find the most recent n backups. For that, I use the backupset table in msdb. So, let’s say I want to list the last 3 backups for each database.

 select database_name, backup_start_date
from (
SELECT database_name, backup_start_date, row_number() over (partition by database_name order by backup_start_date desc) as bkup_number
FROM msdb.dbo.backupset
) as top_n
where bkup_number <= 3 

Now, all we need to do is change that query slightly to get us the min backup date.

 select database_name, min(backup_start_date)
from (
SELECT database_name, backup_start_date, row_number() over (partition by database_name order by backup_start_date desc) as bkup_number
FROM msdb.dbo.backupset
) as top_n
where bkup_number <= 3
group by database_name 

And that date is the cutoff date for deleting backup files for that particular database. So, for example, if we want to retain the most recent 3 backups, we know we can delete any AdventureWorks backups older than 2012-01-22 07:03:41.000.

Now that we know how to get the cutoff date for each database, let’s put it together in the procedure. (Alternatively, you can make this a separate procedure altogether, up to you.) To accomodate this new option, I added a possible unit type: F (for files). So if we execute

 exec [maint].[sp_clean_backupdir2] 3, 'F', 'bak'

we mean to delete all but the last 3 backup files for each database. This new parameter required a minor change to my initialization logic to account for the new possible value.

 SELECT
 @unit2 = CASE @unit
WHEN 'H' THEN 'hh'
WHEN 'D' THEN 'dd'
WHEN 'W' THEN 'ww'
WHEN 'M' THEN 'mm'
WHEN 'Y' THEN 'yy'
WHEN 'F' THEN 'ff'
ELSE @unit
END
,@period = CASE
WHEN @period > 0 and @unit <> 'F' THEN @period*-1
ELSE @period
END
IF @unit2 NOT IN ('hh', 'dd', 'ww', 'mm', 'yy', 'ff')
RAISERROR (N'Invalid interval unit specified.  Accepted values are H,D,W,M,Y, or F.'
,16
,1) 

The major change required meant adding some conditional logic. If the unit parameter isn’t ‘F’, we’ll carry on as we did before. However, if it is ‘F’, then we need to use the logic I described above to get the @deldate for each database. We also go through each individual database’s subfolder(s), rather than just a blanket delete on the BackupPath(s) including all subfolders.

 IF @unit <> 'F'
/* ... the old logic ... */ELSE
BEGIN
DECLARE paths CURSOR FOR
SELECT BackupPath, database_name, min(backup_start_date)
FROM  maint.BackupPaths
CROSS JOIN (
     SELECT database_name, backup_start_date, row_number() over (partition by database_name order by backup_start_date desc) as bkup_number
     FROM msdb.dbo.backupset
    ) as top_n
where bkup_number = @period
group by BackupPath, database_name
OPEN paths
FETCH paths INTO @path, @database_name, @deldate
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @deldate = DATEADD(mi, -1, @deldate)
SELECT @chardate = CONVERT(NVARCHAR(19),@deldate,126)
SELECT @path = @path + '\' + @database_name
EXECUTE master.dbo.xp_delete_file
 0-- 0 = backup file, 1 = report file
,@path-- base folder
,@fileext-- file extension
,@chardate-- older than this date
,0-- 1 = include first-level subfolders, 0 = don't include subfolders
FETCH paths INTO @path, @database_name, @deldate
END
CLOSE paths
DEALLOCATE paths
END
 

And there you go. I hope this helps, Philip. Give it a try and let me know how it works for you. Disclaimer: I whipped it together pretty quickly, so be sure to test it thoroughly before using it in a production environment!

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating