Technical Article

dbWarden - Purge old data

,

This script creates/modifies all objects necessary for a cleanup of dbWarden History.

The default retention is 90 days. If you want to change it or add other tables, go and check table dbo.DataDictionary_Tables where I added 2 columns : one for the column to use (necessarily a date or datetime) and one for the number of days to keep in table.

IF  NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Proc4HistoryTableCleanup]'))
BEGIN
    EXECUTE ('CREATE PROCEDURE [dbo].[Proc4HistoryTableCleanup] ( ' +
'    @TableOwnervarchar(64),' +
            '    @TableName     varchar(512), ' +
            '    @DateFieldName varchar(50), ' +
' @retentionDays INT = 0,' +
'    @Debug     TINYINT = 0' +
            ') ' +
            'AS ' +
            'BEGIN ' +
            '   SELECT ''Not implemented'' ' +
            'END')
END
GO

ALTER PROCEDURE [dbo].[Proc4HistoryTableCleanup] (
@TableOwnervarchar(64),
    @TableName  varchar(64),
    @DateFieldName  varchar(64),
    @retentionDaysINT,
@maxRowCntINT = 10000,
@debug TINYINT = 0
)
AS
/*
 ===================================================================================
  DESCRIPTION:
    This procedure takes in charge the clean-up for a given table based on a date field
given as parameter and a retention expressed as a number of days.
If the number of records to delete is more than a configurable limit, a dichotomic 
delete will be performed so that there is no matter on big open transaction without 
commit.
 
  ARGUMENTS :
@TableOwnername of the owner of the table to clean up
    @TableName     name of the table to clean up
@DateFieldNamename of the date field to use for clean up
@retentionDaysnumber of days to keep track
@maxRowCntmaximal number of rows to consider in an open transaction.
If the cleanup needs more than that number, the query will be cut 
into pieces implying maximum @maxRowCnt
    @debugif set to 1, this enables the debug mode
 
  REQUIREMENTS:
 
  ==================================================================================
  BUGS:
 
    BUGID       Fixed   Description
    ==========  =====   ==========================================================
    ----------------------------------------------------------------------------------
  ==================================================================================
  NOTES:
  AUTHORS:
       .   VBO     Vincent Bouquette   (vincent.bouquette@chu.ulg.ac.be)
       .   BBO     Bernard Bozert      (bernard.bozet@chu.ulg.ac.be)
       .   JEL     Jefferson Elias     (jelias@chu.ulg.ac.be)
 
  COMPANY: CHU Liege
  ==================================================================================
  Revision History
 
    Date        Nom         Description
    ==========  =====       ==========================================================
    18/11/2014  JEL         Version 0.1.0
    ----------------------------------------------------------------------------------
 ===================================================================================
*/
BEGIN

    --SET NOCOUNT ON;

    DECLARE @versionNb        varchar(16) = '0.1.0';
    DECLARE @tsql             nvarchar(max);-- text to execute via dynamic SQL
DECLARE @tmpRetentionDays INT;-- number of retention days to remove 
DECLARE @retentionInc  INT;-- increment for tmpRetentionDays


BEGIN TRY
-- 
-- check parameter :
-- retention days 
--
if @retentionDays < 0
BEGIN
RAISERROR ('Parameter "retentionDays" is negative. Avorting execution', 10,1,@retentionDays);
END

if @retentionDays = 0
return

--
-- check parameters
-- table-related parameters are usable ?
--
IF(NOT EXISTS (
SELECT 1
FROM 
information_schema.COLUMNS 
where 
TABLE_CATALOG= DB_NAME(DB_id()) -- in current database
AND TABLE_SCHEMA = @TableOwner
AND TABLE_NAME   = @TableName
and COLUMN_NAME  = @DateFieldName
  ))
  
BEGIN
RAISERROR('Parameters about the table are unusable in current database ', 10,1,@TableOwner, @TableName ,@DateFieldName )
END

if @debug = 1
BEGIN
PRINT '----------------------------------------------------'
PRINT OBJECT_NAME(@@PROCID)
PRINT '===================================================='
PRINT 'Owner  = ' + @TableOwner
PRINT 'TableName  = ' + @TableName
PRINT 'Column     = ' + @DateFieldName
PRINT 'Retention  = ' + CONVERT (VARCHAR ,@retentionDays)
PRINT 'Max Row Nb = ' + CONVERT (VARCHAR ,@maxRowCnt)
PRINT '----------------------------------------------------'
PRINT CHAR(10)
END

-- 
-- Iteration number evaluation
--
DECLARE @dateThresh DATETIME
SET @dateThresh = GETDATE() - @retentionDays

DECLARE @dateThreshStr   VARCHAR(100)
SELECT @dateThreshStr = convert(varchar(100),@dateThresh,112)

DECLARE @totalRecordNb   BIGINT
DECLARE @MinDateFieldVal DATETIME

SET @tsql =  N'SELECT ' + CHAR(10) +
' @totalRecNb = COUNT_BIG(*),'   + CHAR(10) +
'    @minDate    = MIN([' + @DateFieldName + '])' + CHAR(10) +
'FROM'  + CHAR(10) +
'    [' + @TableOwner + '].[' + @TableName + ']' + CHAR(10) +
'WHERE' + CHAR(10) +
'    '  + @DateFieldName + ' < convert(DATETIME,@dateThresh,112)'

execute sp_executesql @tsql, N'@totalRecNb BIGINT OUTPUT, @minDate DATETIME OUTPUT,@dateThresh VARCHAR(100)', @minDate = @MinDateFieldVal OUTPUT, @totalRecNb = @totalRecordNb OUTPUT, @dateThresh = @dateThreshStr

if @debug = 1
BEGIN
PRINT 'Min Date in table : ' + CONVERT (VARCHAR ,@MinDateFieldVal,112)
PRINT 'Number of records : ' + CONVERT (VARCHAR ,@totalRecordNb)
END

if(@totalRecordNb > @maxRowCnt) 
BEGIN

-- divide by 2 the number of days between min date and the retention period
DECLARE @nbOfDaysToAdd INT
SELECT @nbOfDaysToAdd = (DATEDIFF(day,@MinDateFieldVal,@dateThresh) / 2)

if @debug = 1
BEGIN
PRINT 'Too many records to take care at once !'
PRINT 'Days to play with : ' + CONVERT (VARCHAR ,@nbOfDaysToAdd)
END

-- TODO : if days is not enough => play with hours !

SET @tmpRetentionDays = @retentionDays+@nbOfDaysToAdd
if @debug = 1
BEGIN
PRINT 'New retention : ' + convert(varchar,@tmpRetentionDays)
END

exec [dbo].[Proc4HistoryTableCleanup] @TableOwner=@TableOwner,@TableName=@TableName,@DateFieldName=@DateFieldName,@retentionDays=@tmpRetentionDays,@maxRowCnt=@maxRowCnt,@debug=@debug

if @debug = 1
BEGIN
PRINT 'Restarting procedure with the previous retention :' + CONVERT (varchar,@retentionDays)
END

execute [dbo].[Proc4HistoryTableCleanup] @TableOwner=@TableOwner,@TableName=@TableName,@DateFieldName=@DateFieldName,@retentionDays=@retentionDays,@maxRowCnt=@maxRowCnt,@debug=@debug
END
ELSE
BEGIN
if @debug = 1
BEGIN
PRINT 'Deleting ' + CONVERT (varchar,@totalRecordNb) + ' records' 
END

SET @tsql = 'DELETE' + CHAR(10) +
'FROM'  + CHAR(10) +
'    [' + @TableOwner + '].[' + @TableName + ']' + CHAR(10) +
'WHERE' + CHAR(10) +
'    '  + @DateFieldName + ' < convert(DATETIME,@dateThresh,112)'

execute sp_executesql @tsql, N'@dateThresh VARCHAR(100)', @dateThresh = @dateThreshStr

if @debug = 1
BEGIN
SET @tsql =  N'SELECT ' + CHAR(10) +
' @totalRecNb = COUNT_BIG(*),'   + CHAR(10) +
'    @minDate    = MIN([' + @DateFieldName + '])' + CHAR(10) +
'FROM'  + CHAR(10) +
'    [' + @TableOwner + '].[' + @TableName + ']' + CHAR(10) +
'WHERE' + CHAR(10) +
'    '  + @DateFieldName + ' < convert(DATETIME,@dateThresh,112)'

execute sp_executesql @tsql, N'@totalRecNb BIGINT OUTPUT, @minDate DATETIME OUTPUT,@dateThresh VARCHAR(100)', @minDate = @MinDateFieldVal OUTPUT, @totalRecNb = @totalRecordNb OUTPUT, @dateThresh = @dateThreshStr

PRINT 'Nb of records after delete : ' + CONVERT (varchar,@totalRecordNb)
PRINT 'Minimum date after delete  : ' + CONVERT(varchar,@MinDateFieldVal,112)
END

END


END TRY

BEGIN CATCH
PRINT 'ErrorNumber    : ' + CONVERT (VARCHAR , ERROR_NUMBER())
PRINT 'ErrorSeverity  : ' + CONVERT (VARCHAR , ERROR_SEVERITY())
PRINT 'ErrorState     : ' + CONVERT (VARCHAR , ERROR_STATE())
PRINT 'ErrorProcedure : ' + CONVERT (VARCHAR , ERROR_PROCEDURE())
PRINT 'ErrorLine      : ' + CONVERT (VARCHAR , ERROR_LINE())
PRINT 'ErrorMessage   : ' + CONVERT (VARCHAR , ERROR_MESSAGE()) 
IF @@TRANCOUNT > 0
ROLLBACK TRAN --RollBack in case of Error

RAISERROR ('Unable to proceed !', 10,1,@retentionDays);

END CATCH
END 

/**

Usage example :
==============
exec dbo.Proc4HistoryTableCleanup 
@TableOwner= 'dbo',
@TableName= 'CPUStatsHistory',
@DateFieldName= 'DateStamp',
@retentionDays  = 700,
@debug= 1

exec dbo.Proc4HistoryTableCleanup 
@TableOwner= 'dbo',
@TableName= 'CPUStatsHistory',
@DateFieldName= 'DateStamp',
@retentionDays  = 300,
@debug= 1
*/

alter table  dbo.DataDictionary_Tables 
add retentionDays INT DEFAULT 90

update dbo.DataDictionary_Tables
set retentionDays = null
where TableName in (
'AlertContacts','AlertSettings','DatabaseSettings','DataDictionary_Fields',
'DataDictionary_Tables','SchemaChangeLog','ServerChangeLog'
)

update dbo.DataDictionary_Tables
set retentionDays = 90
where TableName in (
'BlockingHistory','CPUStatsHistory','FileStatsHistory','HealthReport','JobStatsHistory',
'MemoryUsageHistory','PerfStatsHistory','QueryHistory'
)

alter table  dbo.DataDictionary_Tables 
add dateField VARCHAR(100)

update dbo.DataDictionary_Tables
set dateField = 'DateStamp'
where TableName in (
'BlockingHistory','CPUStatsHistory','HealthReport',
'MemoryUsageHistory','QueryHistory'
)
update dbo.DataDictionary_Tables
set dateField = 'FileStatsDateStamp'
where TableName = 'FileStatsHistory'

update dbo.DataDictionary_Tables
set dateField = 'JobStatsDateStamp'
where TableName = 'JobStatsHistory'

update dbo.DataDictionary_Tables
set dateField = 'StatDate'
where TableName = 'PerfStatsHistory'


create view dbo.CleanupSettings
AS
select 
SchemaName,
TableName,
dateField as DateFieldName,
retentionDays
from dbo.DataDictionary_Tables 
where retentionDays is not null;


IF  NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[dbWarden_HistoryCleanup]'))
BEGIN
    EXECUTE ('CREATE PROCEDURE [dbo].[dbWarden_HistoryCleanup] ( ' +
'    @Debug     TINYINT = 0' +
            ') ' +
            'AS ' +
            'BEGIN ' +
            '   SELECT ''Not implemented'' ' +
            'END')
END
GO

ALTER PROCEDURE [dbo].dbWarden_HistoryCleanup (
@debug TINYINT = 0
)
AS
/*
 ===================================================================================
  DESCRIPTION:
    This procedure takes care of purging old data inside tables defined in the
    table or view dbo.CleanupSettings of the same database as the one in which this
    procedure has been created.
 
  ARGUMENTS :
    @debugif set to 1, this enables the debug mode
 
  REQUIREMENTS:
 
  ==================================================================================
  BUGS:
 
    BUGID       Fixed   Description
    ==========  =====   ==========================================================
    ----------------------------------------------------------------------------------
  ==================================================================================
  NOTES:
  AUTHORS:
       .   VBO     Vincent Bouquette   (vincent.bouquette@chu.ulg.ac.be)
       .   BBO     Bernard Bozert      (bernard.bozet@chu.ulg.ac.be)
       .   JEL     Jefferson Elias     (jelias@chu.ulg.ac.be)
 
  COMPANY: CHU Liege
  ==================================================================================
  Revision History
 
    Date        Nom         Description
    ==========  =====       ==========================================================
    19/11/2014  JEL         Version 0.1.0
    ----------------------------------------------------------------------------------
 ===================================================================================
*/
BEGIN

    --SET NOCOUNT ON;

    DECLARE @versionNb        varchar(16) = '0.1.0';
    DECLARE @tsql             nvarchar(max);-- text to execute via dynamic SQL

DECLARE @CurrentOwnerVARCHAR(50)
DECLARE @CurrentTableVARCHAR(50)
DECLARE @CurrentColumnVARCHAR(50)
DECLARE @CurrentRetentionINT

DECLARE getTablesToPurge CURSOR FOR
        SELECT *
        FROM [dbo].[CleanupSettings]        

open getTablesToPurge
    FETCH NEXT
    FROM getTablesToPurge INTO @CurrentOwner,@CurrentTable,@CurrentColumn,@CurrentRetention
    
    WHILE @@FETCH_STATUS = 0
    BEGIN

if @debug = 1 
BEGIN
PRINT 'Current Owner  : '    + @CurrentOwner
PRINT 'Current Table  : '    + @CurrentTable
PRINT 'Current Column : '    + @CurrentColumn + '(datetime column used in where clause)'
PRINT 'Current retention : ' + convert(varchar,@CurrentRetention)
END

exec dbo.Proc4HistoryTableCleanup 
@TableOwner= @CurrentOwner,
@TableName= @CurrentTable,
@DateFieldName= @CurrentColumn,
@retentionDays  = @CurrentRetention,
@debug= @debug

-- carry on ...
        FETCH NEXT
        FROM getTablesToPurge INTO @CurrentOwner,@CurrentTable,@CurrentColumn,@CurrentRetention
    END
    CLOSE getTablesToPurge
    DEALLOCATE getTablesToPurge

END


/**

Usage example :
==============
exec dbo.[dbWarden_HistoryCleanup] 
@debug= 1

exec dbo.[dbWarden_HistoryCleanup]
*/

-- 
-- ------------------------------
-- Sample job : everyday @8:15PM
-- ------------------------------
--
IF NOT EXISTS (SELECT * FROM msdb..sysjobs WHERE name = 'dbWarden_HistoryCleanup')
BEGIN
BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0

DECLARE @jobId BINARY(16)
EXEC 
@ReturnCode =  msdb..sp_add_job @job_name=N'dbWarden_HistoryCleanup', 
@enabled=1, 
@notify_level_eventlog=0, 
@notify_level_email=2, 
@notify_level_netsend=0, 
@notify_level_page=0, 
@delete_level=0, 
@description=N'No description available.', 
@category_name=N'Database Monitoring', 
@owner_login_name=N'sa', 
@notify_email_operator_name=N'SQL_DBA', @job_id = @jobId OUTPUT

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC 
@ReturnCode = msdb..sp_add_jobstep @job_id=@jobId, @step_name=N'run proc', 
@step_id=1, 
@cmdexec_success_code=0, 
@on_success_action=1, 
@on_success_step_id=0, 
@on_fail_action=2, 
@on_fail_step_id=0, 
@retry_attempts=0, 
@retry_interval=0, 
@os_run_priority=0, @subsystem=N'TSQL', 
@command=N'SET NOCOUNT ON
EXEC [DBA].dbo.dbWarden_HistoryCleanup', 
@database_name=N'master', 
@flags=0

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb..sp_update_job @job_id = @jobId, @start_step_id = 1

DECLARE @schedule_id int
EXEC msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'dbWarden_Schedule_HistoryCleanup', 
@enabled=1, 
@freq_type=4, 
@freq_interval=1, 
@freq_subday_type=1, 
@freq_subday_interval=0, 
@freq_relative_interval=0, 
@freq_recurrence_factor=1, 
@active_start_date=20141119, 
@active_end_date=99991231, 
@active_start_time=201500, 
@active_end_time=235959, @schedule_id = @schedule_id OUTPUT

IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb..sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:
END
GO

Rate

3 (2)

You rated this post out of 5. Change rating

Share

Share

Rate

3 (2)

You rated this post out of 5. Change rating