В серии статей, посвященных группам доступности AlwaysOn и заданиям агентов SQL Server, мне уже доводилось излагать принципы, лежащие в основе настоящей статьи. Так что отчасти мне придется повторяться. Однако я пришел к выводу, что тема эта весьма важна и ей стоит посвятить отдельный материал.

Как я отмечал в статьях упомянутого выше цикла, многие администраторы баз данных на дух не переносят планы обслуживания SQL Server. И, конечно, чем больше опыта у меня за плечами, тем труднее мне отказаться от мысли, что практически все задания, включенные в планы обслуживания, подготовлены стажерами (или людьми, не читавшими рекомендаций по работе с SQL Server). Рассмотрим для примера задание Rebuild Index Task. При его выполнении невозможно указать, какой уровень фрагментации влечет за собой необходимость выполнения процедуры Rebuild. Иными словами, если вы настроите задание для выполнения в определенной базе данных, оно перестроит все индексы вне зависимости от того, есть в том необходимость или нет. Если вы случайно выполните задание два раза подряд, все ваши индексы будут перестроены, и опять же дважды.

Соответственно, если задания, предназначенные для включения в планы обслуживания, в большинстве своем не выдерживают никакой критики, то базовый процессор, используемый для обработки планов обслуживания, всегда производил на меня благоприятное впечатление, поскольку его отличают мощность и богатые функциональные возможности. А это, разумеется, всякому очевидно, ибо здесь мы говорим о SSIS. К тому же я всегда с симпатией относился к логике и механизму реализации, воплощенным в заданиях Back Up Database Tasks. Пусть они явно не дотягивают до уровня некоторых продуктов сторонних производителей, представленных на рынке, пусть сценарии Олла Халленгрена, как утверждается, превосходят их во многих отношениях. Однако резервные копии планов обслуживания характеризуются, помимо прочего, простотой и логической стройностью, которая меня всегда подкупала. К примеру, способность размещать резервные копии той или иной базы данных в особую папку представляет собой, с моей точки зрения, большое достижение. Предположим, в моей организации произошел аварийный сбой, при этом у меня нет желания задействовать графический интерфейс и Backup History для выяснения, какие именно файлы мне потребуются. Так вот, можете мне поверить: то обстоятельство, что все эти файлы свалены в гигантскую «кучу-малу», не будет вызывать в моей душе восторга. А вот возможность безо всяких затруднений выполнять операции по удалению просроченных резервных копий (через задание Maintenance Cleanup Task), на мой взгляд, очень ценна. Есть что-то простое и изящное в решении, в соответствии с которым вы просто указываете определенное число дней (или часов) в качестве срока хранения резервных копий базы данных.

Замена резервных копий плана обслуживания сценарием

С другой стороны, планы обслуживания действительно имеют свои недостатки. Так, трудно (хотя и возможно) назначить владельцем плана обслуживания учетную запись SysAdmin. К тому же пусть возможности службы SSIS и весьма широки, сама идея «переноса» планов обслуживания с одного сервера на другой (или постоянной синхронизации их на нескольких серверах) сопряжена с такими серьезными затруднениями, что некоторое время назад я взялся за составление ряда сценариев. Эти сценарии в функциональном отношении в большей или меньшей степени обеспечивают те же ключевые преимущества, которые предоставляют планы обслуживания, позволяя при этом обходиться без таких планов.

Приведу один из этих сценариев, используемый мной для экземпляров, не относящихся к категории SQL Server Express (см. листинг 1).

Как видите, все довольно просто: вы указываете, какие базы данных или типы баз данных хотите зарезервировать и какие типы резервных копий собираетесь запускать, а также вводите информацию о пути и детали «сохранения». В противном случае система выдаст резервные копии, практически неотличимые от тех, что генерируются с помощью функции Maintenance Plan Backup (вплоть до имен файлов). Хотя если присмотреться внимательнее, вы заметите, что некоторые средства и возможности в вашей копии не представлены. Если без них вам трудно обойтись, реализуйте их по мере необходимости.

Я использовал аналогичный подход при выполнении резервных копий в копиях SQL Server Express, но фактически удалял возможность WITH COMPRESS и изменял сигнатуру хранимой процедуры, чтобы несколько облегчить доступ к ней через файлы. bat или. ps1 (см. листинг 2).

Используя параметр @CleanupTime (в часах), я избавляю себя от необходимости дополнительно запускать запрос или операцию с целью указания параметра @olderThan, как в сценарии листинга 1. А это в свою очередь означает, что я могу удерживать все необходимые данные для вызова данной версии SQL Express из одной строки. Например, следующую вставку я вношу в файл. bat, чтобы обеспечить получение полных резервных копий пользовательских баз данных на экземпляре SQL Server Express:

REM FULL Backups of User DBs:
osql -S. -E -Q "EXEC
   dbo.dba_DatabaseBackups
   @BackupType = 'FULL',
   @DatabasesToBackup = ' [USER_DBS]',
   @BackupDirectory = 'D:\SQLBackups\User',
   @CleanupTime = 72;"

Можно использовать и резервные копии T-Log, но с @BackupType со значением ‘LOG’. В таком случае останется только настроить задание с помощью планировщика Windows Task Scheduler, чтобы обеспечить выполнение.

Листинг 1. Сценарий замены плана обслуживания
/*

        -- Эта хранимая процедура замещает резервные копии
        SQL Server Maintenance Task.
        -- В нее достаточно вставить путь, список dbs, который нужно
        зарезервировать, и указать категорию резервной копии...
        --               а также проставить  временную метку для удаления
        данных, введенных ранее времени X.

        -- ВНИМАНИЕ: на Express и Web ... сжатие не поддерживается

        -- Резервные копии системных баз данных:
        DECLARE @olderThan datetime;
        SET @olderThan = DATEADD(dd, -3, GETDATE());

        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'FULL',
                @DatabasesToBackup = '[SYSTEM_DBS]',
                @BackupDirectory = 'D:\SQLBackups\System',
                @OlderBackupDeletionTime = @olderThan;
        GO

        -- Полные резервные копии всех пользовательских баз данных:
        DECLARE @olderThan datetime;
        SET @olderThan = DATEADD(hh, -48, GETDATE());
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'FULL',
                @DatabasesToBackup = '[USER_DBS]',
                @BackupDirectory = 'D:\SQLBackups\User',
                @OlderBackupDeletionTime = @olderThan;
        GO

        -- Полные резервные копии указанных пользовательских
        баз данных:
        DECLARE @olderThan datetime;
        SET @olderThan = DATEADD(hh, 25, GETDATE());
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'FULL',
                @DatabasesToBackup = 'meddling,ssv2',
                @BackupDirectory = 'D:\SQLBackups\User',
                @OlderBackupDeletionTime = @olderThan;
        GO

        -- Разностные резервные копии указанных баз данных:
        DECLARE @olderThan datetime;
        SET @olderThan = DATEADD(hh, -48, GETDATE());
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'DIFF',
                @DatabasesToBackup = 'meddling,ssv2',
                @BackupDirectory = 'D:\SQLBackups\User',
                @OlderBackupDeletionTime = @olderThan;
        GO

        -- Резервные копии T-Log всех пользовательских баз данных:
        DECLARE @olderThan datetime;
        SET @olderThan = DATEADD(hh, -36, GETDATE());
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'LOG',
                @DatabasesToBackup = '[USER_DBS]',
                @BackupDirectory = 'D:\SQLBackups\User',
                @OlderBackupDeletionTime = @olderThan;
        GO
*/

USE master;
GO
IF OBJECT_ID('dbo.dba_DatabaseBackups','P') IS NOT NULL
        DROP PROC dbo.dba_DatabaseBackups;
GO

CREATE PROC dbo.dba_DatabaseBackups
        @BackupType                                     sysname,
        @DatabasesToBackup                      nvarchar(1000),
        @BackupDirectory                        sysname,
        @OlderBackupDeletionTime        datetime,
        @PrintOnly                                      bit             = 0
AS
        SET NOCOUNT ON;
        DECLARE @jobStart datetime;
        SET @jobStart = GETDATE();

        -- проверка
        IF UPPER(@BackupType) NOT IN ('FULL', 'DIFF','LOG') BEGIN
                PRINT 'Usage: @BackupType = FULL|DIFF|LOG';
                RAISERROR('Invalid @BackupType Specified.', 16, 1);
        END

        IF @OlderBackupDeletionTime >= GETDATE() BEGIN
                RAISERROR('Invalid @OlderBackupDeletionTime - greater
                than or equal to NOW.', 16, 1);
        END

        -- определяем базы данных:
        DECLARE @targetDatabases TABLE (
                database_name sysname NOT NULL
        );

        IF UPPER(@DatabasesToBackup) = '[SYSTEM_DBS]' BEGIN
                INSERT INTO @targetDatabases (database_name)
                SELECT 'master' UNION SELECT 'msdb' UNION SELECT
                'model';
        END

        IF UPPER(@DatabasesToBackup) = '[USER_DBS]' BEGIN

                IF @BackupType = 'LOG'
                        INSERT INTO @targetDatabases (database_name)
                        SELECT name FROM sys.databases
                        WHERE recovery_model_desc = 'FULL'
                                AND name NOT IN ('master', 'model', 'msdb',
                                'tempdb')
                        ORDER BY name;
                ELSE
                        INSERT INTO @targetDatabases (database_name)
                        SELECT name FROM sys.databases
                        WHERE name NOT IN ('master', 'model', 'msdb','tempdb')
                        ORDER BY name;
        END
        IF (SELECT COUNT(*) FROM @targetDatabases) <= 0 BEGIN
                -- десериализуем список баз данных, подлежащих
                резервированию:
                SELECT TOP 400 IDENTITY(int, 1, 1) as N
                INTO #Tally
                FROM sys.columns;

                DECLARE @SerializedDbs nvarchar(1200);
                SET @SerializedDbs = ',' + REPLACE(@DatabasesToBackup,
                ' ', '') + ',';

                INSERT INTO @targetDatabases (database_name)
                SELECT SUBSTRING(@SerializedDbs, N + 1, CHARINDEX(',',
                @SerializedDbs, N + 1) - N - 1)
                FROM #Tally
                WHERE N < LEN(@SerializedDbs)
                        AND SUBSTRING(@SerializedDbs, N, 1) = ',';

                IF @BackupType = 'LOG' BEGIN
                        DELETE FROM @targetDatabases
                        WHERE database_name NOT IN (
                                SELECT name FROM sys.databases WHERE
                                recovery_model_desc = 'FULL'
                        );
                  END
                ELSE
                        DELETE FROM @targetDatabases
                        WHERE database_name NOT IN (SELECT name FROM
                        sys.databases);
        END

        -- удостоверяемся в том, что мы что-то получили:
        IF (SELECT COUNT(*) FROM @targetDatabases) <= 0 BEGIN
                PRINT 'Usage: @DatabasesToBackup =
                [SYSTEM_DBS]|[USER_DBS]|dbname1,dbname2,dbname3,etc';
                RAISERROR('No databases for backup.', 16, 1);
        END

        -- нормализуем путь:
        IF(RIGHT(@BackupDirectory, 1) = ‘\’)
                SET @BackupDirectory = LEFT(@BackupDirectory,
                LEN(@BackupDirectory) - 1);

        -- Начинаем резервные копии (???):
        DECLARE backups  FAST_FORWARD FOR
        SELECT
                database_name
        FROM
                @targetDatabases
        ORDER BY
                database_name;

        DECLARE @currentDB sysname;
        DECLARE @backupPath sysname;
        DECLARE @backupStatement nvarchar(2000);
        DECLARE @backupName sysname;
        DECLARE @now datetime;
        DECLARE @timestamp sysname;
        DECLARE @extension sysname;
        DECLARE @offset sysname;
        DECLARE @verifyStatement nvarchar(2000);
        DECLARE @Errors TABLE (
                ErrorID int IDENTITY(1,1) NOT NULL,
                [Database] sysname NOT NULL,
                ErrorMessage nvarchar(2000)
        );
        DECLARE @ErrorMessage sysname;

        OPEN backups;
        FETCH NEXT FROM backups INTO @currentDB;

        WHILE @@FETCH_STATUS = 0 BEGIN
              
                SET @backupPath = @BackupDirectory + N'\' + @currentDB;
                -- удостоверяемся в том, что подкаталог существует:
                IF @PrintOnly = 1 BEGIN
                        PRINT 'Verify/Create Directory: ' + @backupPath;
                  END
                ELSE
                        EXECUTE master.dbo.xp_create_subdir @backupPath;
                -- создаем имя резервной копии:
                SET @extension = ‘.bak’;
                IF @BackupType = 'LOG'
                        SET @extension = '.trn';

                SET @now = GETDATE();
                SET @timestamp = REPLACE(REPLACE(REPLACE(CONVERT
                (sysname, @now, 120), '-','_'), ':',''), ' ', '_');
                SET @offset = RIGHT(CAST(CAST(RAND() AS decimal(12,11))
                AS varchar(20)),7);
                SET @backupName = @currentDB + '_backup_' + @timestamp
                + '_' + @offset + @extension;

                -- главное отличие данной резервной копии от резервной
                копии плана обслуживания: CHECKSUM...
                SET @backupStatement = 'BACKUP  ' + QUOTENAME
                (@currentDB, '[]') + ' TO DISK = N''' + @backupPath + '\' +
                @backupName + '''
        WITH  COMPRESSION, NOFORMAT, NOINIT, NAME = N''' +
        @backupName + ''', SKIP, REWIND, NOUNLOAD, CHECKSUM,
        STATS = 25;'

                IF @BackupType IN ('FULL', 'DIFF') BEGIN
                        SET @backupStatement = REPLACE(@backupStatement,
                        '', 'DATABASE');

                        IF @BackupType = 'DIFF'
                                SET @backupStatement = REPLACE
                                (@backupStatement, '', 'DIFFERENTIAL,');
                        ELSE
                                SET @backupStatement = REPLACE
                                (@backupStatement, '{1}', '');
                  END
                ELSE BEGIN -- log file backup
                        SET @backupStatement = REPLACE
                        (@backupStatement, '', 'LOG');
                        SET @backupStatement = REPLACE(@backupStatement,
                        '', '');
                END

                SET @verifyStatement = 'RESTORE VERIFYONLY FROM DISK
                = N''' + @backupPath + '\' + @backupName + ''' WITH
                NOUNLOAD, NOREWIND;';

                BEGIN TRY
                        IF @PrintOnly = 1 BEGIN
                                PRINT @backupStatement;
                                PRINT @verifyStatement;
                          END
                        ELSE BEGIN
                                EXEC sp_executesql @backupStatement;
                                EXEC sp_executesql @verifyStatement;
                        END
                END TRY
                BEGIN CATCH
                        SELECT @ErrorMessage = ERROR_MESSAGE();
                        INSERT INTO @Errors ([Database], ErrorMessage)
                        VALUES  (@currentDB, @ErrorMessage);
                END CATCH

                FETCH NEXT FROM backups INTO @currentDB;
        END;
        CLOSE backups;
        DEALLOCATE backups;

        -- Осуществляем удаление любых/всех файлов
        по мере необходимости:
        DECLARE @deleteStatement nvarchar(2000);
        SET @deleteStatement = 'EXECUTE master.dbo.xp_delete_file 0,
        N''' + @BackupDirectory + ''', N''' + REPLACE(@extension, '.','') +
        ''', N''' + REPLACE(CONVERT(nvarchar(20),
        @OlderBackupDeletionTime, 120), ' ', 'T') + ''', 1;';
        BEGIN TRY
                IF @PrintOnly = 1
                        PRINT @deleteStatement
                ELSE
                        EXEC sp_executesql @deleteStatement;
        END TRY
        BEGIN CATCH
                SELECT @ErrorMessage = ERROR_MESSAGE();

                INSERT INTO @Errors ([Database], ErrorMessage)
                VALUES  ('File Deletion', @ErrorMessage);
        END CATCH

        IF (SELECT COUNT(*) FROM @Errors) > 0 BEGIN
                PRINT 'The Following Errors were Detectected: ';
                DECLARE errors  FAST_FORWARD FOR
                SELECT [Database],[ErrorMessage]
                FROM @Errors
                ORDER BY ErrorID;

                OPEN errors;
                FETCH NEXT FROM errors INTO @currentDB, @ErrorMessage;

                WHILE @@FETCH_STATUS = 0 BEGIN
                        PRINT 'DATABASE/OPERATION: ' + @currentDB + ' -> ' +
                        @ErrorMessage;
                      
                        FETCH NEXT FROM errors INTO @currentDB,
                        @ErrorMessage;
                END

                CLOSE errors;
                DEALLOCATE errors;

                -- Инициируем ошибку, чтобы знать, что возникли
                проблемы:
                RAISERROR('Unexpected errors executing backups - see
                output.', 16, 1);
        END

        RETURN 0;
GO
Листинг 2. Сценарий замены резервных копий заданий SQL Server Maintenance Task
/*

        -- Эта хранимая процедура замещает резервные копии заданий
        SQL Server Maintenance Task.


        -- В нее достаточно вставить путь, список dbs, который нужно
        зарезервировать, и указать категорию резервной копии...
        --               а также проставить  временную метку для удаления
        данных, введенных ранее времени X.

        -- Резервные копии системных баз данных:
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'FULL',
                @DatabasesToBackup = '[SYSTEM_DBS]',
                @BackupDirectory = 'D:\SQLBackups\System',
                @CleanupTime = 72;
        GO

        -- Полные копии ВСЕХ пользовательских баз данных:
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'FULL',
                @DatabasesToBackup = '[USER_DBS]',
                @BackupDirectory = 'D:\SQLBackups\User',
                @CleanupTime = 48;
        GO

        -- Полные резервные копии указанных пользовательских
        баз данных:
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'FULL',
                @DatabasesToBackup = 'meddling,ssv2',
                @BackupDirectory = 'D:\SQLBackups\User',
                @CleanupTime = 25;
        GO
        -- разностные резервные копии указанных баз данных:
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'DIFF',
                @DatabasesToBackup = 'meddling,ssv2',
                @BackupDirectory = 'D:\SQLBackups\User',
                @CleanupTime = 48;
        GO

        -- Резервные копии T-Log всех пользовательских баз данных:
        EXEC dbo.dba_DatabaseBackups
                @BackupType = 'LOG',
                @DatabasesToBackup = '[USER_DBS]',
                @BackupDirectory = 'D:\SQLBackups\User',
                @CleanupTime = 36;
        GO

*/


USE master;
GO

IF OBJECT_ID('dbo.dba_DatabaseBackups','P') IS NOT NULL
        DROP PROC dbo.dba_DatabaseBackups;
GO
CREATE PROC dbo.dba_DatabaseBackups
        @BackupType                                     sysname,
        @DatabasesToBackup                      nvarchar(1000),
        @BackupDirectory                        sysname,
        @CleanupTime                            int             = 72,  -- в часах...
        @PrintOnly                                      bit             = 0
AS
        SET NOCOUNT ON;

        DECLARE @jobStart datetime;
        SET @jobStart = GETDATE();

        -- проверяем
        IF UPPER(@BackupType) NOT IN ('FULL', 'DIFF','LOG') BEGIN
                PRINT 'Usage: @BackupType = FULL|DIFF|LOG';
                RAISERROR('Invalid @BackupType Specified.', 16, 1);
        END

        -- переводим часовые настройки:
        DECLARE @OlderBackupDeletionTime datetime;
        SET @OlderBackupDeletionTime = DATEADD(hh, 0 - @CleanupTime,
        GETDATE());


        IF @OlderBackupDeletionTime >= GETDATE() BEGIN
                RAISERROR(‘Invalid @OlderBackupDeletionTime - greater than
                or equal to NOW.’, 16, 1);
        END

        -- определяем базы данных:
        DECLARE @targetDatabases TABLE (
                database_name sysname NOT NULL
        );

        IF UPPER(@DatabasesToBackup) = '[SYSTEM_DBS]' BEGIN
                INSERT INTO @targetDatabases (database_name)
                SELECT 'master' UNION SELECT 'msdb' UNION SELECT
                'model';
        END
        IF UPPER(@DatabasesToBackup) = '[USER_DBS]' BEGIN

                IF @BackupType = 'LOG'
                        INSERT INTO @targetDatabases (database_name)
                        SELECT name FROM sys.databases
                        WHERE recovery_model_desc = 'FULL'
                                AND name NOT IN ('master', 'model', 'msdb',
                                'tempdb')
                        ORDER BY name;
                ELSE
                        INSERT INTO @targetDatabases (database_name)
                        SELECT name FROM sys.databases
                        WHERE name NOT IN ('master', 'model', 'msdb','tempdb')
                        ORDER BY name;
        END
        IF (SELECT COUNT(*) FROM @targetDatabases) <= 0 BEGIN
                -- десериализуем список баз данных, подлежащих
                резервному копированию:
                SELECT TOP 400 IDENTITY(int, 1, 1) as N
                INTO #Tally
                FROM sys.columns;

                DECLARE @SerializedDbs nvarchar(1200);
                SET @SerializedDbs = ',' + REPLACE(@DatabasesToBackup, ' ',
                '') + ',';
                INSERT INTO @targetDatabases (database_name)
                SELECT SUBSTRING(@SerializedDbs, N + 1, CHARINDEX(',',
                @SerializedDbs, N + 1) - N - 1)
                FROM #Tally
                WHERE N < LEN(@SerializedDbs)
                        AND SUBSTRING(@SerializedDbs, N, 1) = ',';
                IF @BackupType = 'LOG' BEGIN
                        DELETE FROM @targetDatabases
                        WHERE database_name NOT IN (
                                SELECT name FROM sys.databases WHERE
                                recovery_model_desc = 'FULL'
                        );
                  END
                ELSE
                        DELETE FROM @targetDatabases
                        WHERE database_name NOT IN (SELECT name FROM
                        sys.databases);
        END

        -- удостоверяемся в том, что мы что-то получили:
        IF (SELECT COUNT(*) FROM @targetDatabases) <= 0 BEGIN
                PRINT 'Usage: @DatabasesToBackup =
                [SYSTEM_DBS]|[USER_DBS]|dbname1,dbname2,dbname3,etc';
                RAISERROR('No databases for backup.', 16, 1);
        END

        -- нормализуем путь:
        IF(RIGHT(@BackupDirectory, 1) = ‘\’)
                SET @BackupDirectory = LEFT(@BackupDirectory,
                LEN(@BackupDirectory) - 1);

        -- Начинаем резервные копии (???):
        DECLARE backups  FAST_FORWARD FOR
        SELECT
                database_name
        FROM
                @targetDatabases
        ORDER BY
                database_name;
        DECLARE @currentDB sysname;
        DECLARE @backupPath sysname;
        DECLARE @backupStatement nvarchar(2000);
        DECLARE @backupName sysname;
        DECLARE @now datetime;
        DECLARE @timestamp sysname;
        DECLARE @extension sysname;
        DECLARE @offset sysname;
        DECLARE @verifyStatement nvarchar(2000);
        DECLARE @Errors TABLE (
                ErrorID int IDENTITY(1,1) NOT NULL,
                [Database] sysname NOT NULL,
                ErrorMessage nvarchar(2000)
        );
        DECLARE @ErrorMessage sysname;
        OPEN backups;
        FETCH NEXT FROM backups INTO @currentDB;

        WHILE @@FETCH_STATUS = 0 BEGIN
              
                SET @backupPath = @BackupDirectory + N'\' + @currentDB;

                -- удостоверяемся в том, что подкаталог существует:
                IF @PrintOnly = 1 BEGIN
                        PRINT 'Verify/Create Directory: ' + @backupPath;
                  END
                ELSE
                        EXECUTE master.dbo.xp_create_subdir @backupPath;
                -- создаем имя резервной копии:
                SET @extension = ‘.bak’;
                IF @BackupType = 'LOG'
                        SET @extension = '.trn';

                SET @now = GETDATE();
                SET @timestamp = REPLACE(REPLACE(REPLACE(CONVERT
                (sysname, @now, 120), '-','_'), ':',''), ' ', '_');
                SET @offset = RIGHT(CAST(CAST(RAND() AS decimal(12,11))
                AS varchar(20)),7);

                SET @backupName = @currentDB + '_backup_' +
                @timestamp + '_' + @offset + @extension;


                -- главное отличие данной резервной копии от резервной
                копии плана обслуживания: CHECKSUM...
                SET @backupStatement = 'BACKUP  ' + QUOTENAME
                (@currentDB, '[]') + ' TO DISK = N''' + @backupPath + '\' +
                @backupName + '''
        WITH {1} NOFORMAT, NOINIT, NAME = N''' + @backupName + ''',
        SKIP, REWIND, NOUNLOAD, CHECKSUM;'

                IF @BackupType IN ('FULL', 'DIFF') BEGIN
                        SET @backupStatement = REPLACE(@backupStatement,
                        '', 'DATABASE');

                        IF @BackupType = 'DIFF'
                                SET @backupStatement = REPLACE
                                (@backupStatement, '', 'DIFFERENTIAL,');
                        ELSE
                                SET @backupStatement = REPLACE
                                (@backupStatement, '', '');
                  END
                ELSE BEGIN -- log file backup
                        SET @backupStatement = REPLACE
                        (@backupStatement, '{0}', 'LOG');
                        SET @backupStatement = REPLACE
                        (@backupStatement, '', '');
                END

                SET @verifyStatement = 'RESTORE VERIFYONLY FROM DISK
                = N''' + @backupPath + '\' + @backupName + '''
                WITH NOUNLOAD, NOREWIND;';

                BEGIN TRY
                        IF @PrintOnly = 1 BEGIN
                                PRINT @backupStatement;
                                PRINT @verifyStatement;
                          END
                        ELSE BEGIN
                                EXEC sp_executesql @backupStatement;
                                EXEC sp_executesql @verifyStatement;
                        END

                END TRY
                BEGIN CATCH
                        SELECT @ErrorMessage = ERROR_MESSAGE();

                        INSERT INTO @Errors ([Database], ErrorMessage)
                        VALUES  (@currentDB, @ErrorMessage);
                END CATCH

                FETCH NEXT FROM backups INTO @currentDB;
        END;
        CLOSE backups;
        DEALLOCATE backups;

        -- Теперь удаляем любые/все файлы по мере необходимости:
        DECLARE @deleteStatement nvarchar(2000);
        SET @deleteStatement = 'EXECUTE master.dbo.xp_delete_file 0,
        N''' + @BackupDirectory + ''', N''' + REPLACE(@extension, '.','') +
        ''', N''' + REPLACE(CONVERT(nvarchar(20),
        @OlderBackupDeletionTime, 120), ' ', 'T') + ''', 1;';
        BEGIN TRY
                IF @PrintOnly = 1
                        PRINT @deleteStatement
                ELSE
                        EXEC sp_executesql @deleteStatement;
        END TRY
        BEGIN CATCH
                SELECT @ErrorMessage = ERROR_MESSAGE();

                INSERT INTO @Errors ([Database], ErrorMessage)
                VALUES  ('File Deletion', @ErrorMessage);
        END CATCH

        IF (SELECT COUNT(*) FROM @Errors) > 0 BEGIN
                PRINT 'The Following Errors were Detectected: ';
                DECLARE errors  FAST_FORWARD FOR
                SELECT [Database],[ErrorMessage]
                FROM @Errors
                ORDER BY ErrorID;

                OPEN errors;
                FETCH NEXT FROM errors INTO @currentDB, @ErrorMessage;

                WHILE @@FETCH_STATUS = 0 BEGIN
                        PRINT 'DATABASE/OPERATION: ' + @currentDB + ' -> ' +
                        @ErrorMessage;
                      
                        FETCH NEXT FROM errors INTO @currentDB,
                        @ErrorMessage;
                END

                CLOSE errors;
                DEALLOCATE errors;

                -- Инициируем ошибку, чтобы знать, что возникли
                проблемы:
                RAISERROR('Unexpected errors executing backups - see
                output.', 16, 1);
        END
        RETURN 0;
GO