2

I am trying to change a stored procedure in our SQL environment which is used to create backup jobs remotely. Below is the procedure

/****** Object:  StoredProcedure [dbo].[CreateBackupJobGroupBFull]    Script Date: 8/06/2016 3:18:25 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO


ALTER PROCEDURE [dbo].[CreateBackupJobGroupAFull]   
(@servername nvarchar(100))  
AS  

declare @commandstr nvarchar(max)
DECLARE @SQL nvarchar(4000)
DECLARE @TableExists bit
DECLARE @recp nvarchar(100)
DECLARE @error_msg varchar(300)
declare @groupAfull nvarchar(150)
declare @schedulestr nvarchar(150)
DECLARE @pripathstr VARCHAR(1256)
DECLARE @secpathstr VARCHAR(1256)
declare @defaultscheduleStart varchar(30)
declare @gstr varchar(5)
declare @g varchar(1)

set @groupAfull = 'DBABackupJobGroupA_Full'
set @recp = '**************'
set @defaultscheduleStart = '230000'
set @gstr = '_GA_F'
set @g = 'A'

SET @SQL = 'SELECT @TableExists = CASE WHEN TableExists = 0 THEN 0 ELSE 1 END
            FROM OPENQUERY(' + QUOTENAME(@servername) 
            + ', ''SELECT TableExists = COUNT(*) 
                    from msdb.dbo.sysjobs
                    WHERE name = '''' + @groupAfull + '''''');';

begin try
                        EXECUTE sp_executesql @SQL, N'@TableExists BIT OUTPUT', @TableExists OUT;

END TRY
BEGIN CATCH
                        print 'error message is:'
                        print error_message()
                        set @error_msg='Error occurred when executing CreatBackupJob against server ' + @ServerName + '. Failed to check control table locally. The error message is: ' + error_message()

                        EXEC msdb.dbo.sp_send_dbmail
                        @profile_name = 'Internal mail',
                        @recipients = @recp,
                        @body = @error_msg ,
                        @subject = 'Error occurred on reporting server' ;
END CATCH                       

IF (@TableExists = 0 )
BEGIN

            --add new job for the instance
            set @sql = 'if not exists (select * from [' + @ServerName + '].msdb.dbo.sysjobs where name = ''' + @groupAfull + ''')
            BEGIN
            EXEC [' + @ServerName + '].msdb.dbo.sp_add_job
            @job_name = ''' + @groupAfull + ''', 
            @enabled = 1,
            @description = N''DBA backup job. DO NOT REMOVE. V1.5 (2015-06-16)'',
            @owner_login_name = N''sa''
            EXEC [' + @ServerName + '].msdb.dbo.sp_add_jobserver
            @job_name = ''' + @groupAfull + ''', 
            @server_name = ''(local)''
            end
            '

            begin try
                        EXEC sp_executeSQL @sql

            END TRY
            BEGIN CATCH
                        print 'error message is:'
                        print error_message()
                        set @error_msg='Error occurred when executing CreatBackupJob against server ' + @ServerName + '. Failed to create new job'+ @groupAfull +'. The error message is: ' + error_message()

                        EXEC msdb.dbo.sp_send_dbmail
                        @profile_name = 'Internal mail',
                        @recipients = @recp,
                        @body = @error_msg ,
                        @subject = 'Error occurred on reporting server' ;
            END CATCH       

SET @pripathstr = (SELECT Top 1 [PrimDestinationfull] FROM [SQLADMIN].[dbo].[BackupDestination])
SET @secpathstr = (SELECT Top 1 [SecDestinationfull] FROM [SQLADMIN].[dbo].[BackupDestination])

            --add job step 1 'test'
            set @commandstr = '
DECLARE @dbname VARCHAR(150) -- database name  
DECLARE @pripath VARCHAR(1256) -- path for backup files  
DECLARE @secpath VARCHAR(1256) -- path for backup files  
DECLARE @path VARCHAR(1256) -- path for backup files  
DECLARE @fileName VARCHAR(1256) -- filename for backup  
DECLARE @fileDate VARCHAR(40) -- used for file name
DECLARE @DBGROUP varchar(1)
DECLARE @filecount int
DECLARE @lastbackuptime datetime

SET @DBGROUP = ''''' + @g + '''''

IF not EXISTS (SELECT [name] FROM msdb.sys.tables WHERE  [name] = ''''DBBackupControlTbl''''  )  
    begin   
        raiserror(''''Control table not found!'''', 20, -1) with log
        return 
    end 
else

    if (select count(*) from msdb.dbo.DBBackupControlTbl where BackupGroup = @DBGROUP) > 0 

    -- specify database backup directory
    SET @pripath =  ''''' + @pripathstr + '''''
    SET @secpath =  ''''' + @secpathstr + '''''


    if object_id(''''tempdb.dbo.#fileExist'''') is not null
          drop table #fileExist
    create table #fileExist (
          fileExists int,
          fileIsDir int,
          parentDirExists int
          )
    insert into #fileExist
    exec xp_fileexist @pripath

    if object_id(''''tempdb.dbo.#fileExist2'''') is not null
          drop table #fileExist2
    create table #fileExist2 (
          fileExists int,
          fileIsDir int,
          parentDirExists int
          )
    insert into #fileExist2
    exec xp_fileexist @secpath

    if (select count(*) from #fileExist where fileIsDir = 1) > 0 
        begin
        set @path = @pripath
        end
    ELSE 
        begin
            if (select count(*) from #fileExist2 where fileIsDir = 1) > 0
            begin
                set @path = @secpath
            end
        ELSE
            begin
                raiserror(''''None of the backup directory can be accessed at the moment!'''', 20, -1) with log
                return 
            end
        end

    set @filecount = 999
    while @filecount >= 15
    begin
        IF OBJECT_ID(''''tempdb..#DirectoryTree'''') IS NOT NULL
              DROP TABLE #DirectoryTree;

        CREATE TABLE #DirectoryTree (
               id int IDENTITY(1,1)
              ,subdirectory nvarchar(512)
              ,depth int
              ,isfile bit);

        INSERT #DirectoryTree (subdirectory,depth,isfile)
        EXEC master.sys.xp_dirtree @path,1,1;

        select @filecount = COUNT(*) FROM #DirectoryTree
        WHERE isfile = 1 AND RIGHT(subdirectory,4) = ''''.sts''''

        if @filecount >= 15
            begin
            print ''''Wait for status file to be cleared.''''
            waitfor delay ''''00:00:10''''
            end
        else
            begin
            print ''''Backing up now.''''

            DECLARE @statusfile AS VARCHAR(150)
            DECLARE @cmd AS VARCHAR(150)



    -- specify filename format
    SELECT @fileDate = replace(replace(CONVERT(VARCHAR(40),GETDATE(),120), '''':'''', ''''-'''' ), '''' '''', ''''T'''')

    set @statusfile = @path + replace(@@SERVERNAME, ''''\'''', ''''-'''')  + ''''_'''' + @fileDate + '''''+ @gstr + ''''' +  ''''.STS''''
    set @cmd = ''''echo '''' +  @filedate + '''' > '''' + @statusfile
    EXECUTE Master.dbo.xp_CmdShell  @Cmd


    DECLARE db_cursor CURSOR FOR  
    select databasename from msdb.dbo.DBBackupControlTbl where BackupGroup = @DBGROUP

    OPEN db_cursor   
    FETCH NEXT FROM db_cursor INTO @dbname   

    WHILE @@FETCH_STATUS = 0 
        BEGIN   
       SET @fileName = @path + replace(@@SERVERNAME, ''''\'''', ''''-'''') + ''''_'''' + @dbname + ''''_'''' + @fileDate + '''''+ @gstr + ''''' +  ''''.BAK''''  
       BACKUP DATABASE @dbname TO DISK = @fileName  
       FETCH NEXT FROM db_cursor INTO @dbname   
END 

    CLOSE db_cursor   
    DEALLOCATE db_cursor

    set @cmd = ''''del '''' + @statusfile
    EXECUTE Master.dbo.xp_CmdShell  @Cmd

    end
    continue
    end
'
            print @commandstr

            set @sql = 'if not exists (select * from [' + @ServerName + '].msdb.dbo.sysjobsteps where step_name = ''' + @groupAfull + ''')
            BEGIN 
            EXEC [' + @ServerName + '].msdb.dbo.sp_add_jobstep
            @job_name = ''' + @groupAfull + ''', 
            @step_name = ''' + @groupAfull + ''',
            @subsystem = N''TSQL'',
            @command = N''' + @commandstr + ''',
            @retry_attempts = 3,
            @retry_interval = 1,
            @on_success_action = 1 ,
            @on_fail_action= 2;
            end 
            '
            begin try
                        EXEC sp_executeSQL @sql

            END TRY
            BEGIN CATCH
                        print 'error message is:'
                        print error_message()
                        set @error_msg='Error occurred when executing CreatBackupJob against server ' + @ServerName + '. Failed to create new job step '+ @groupAfull +'. The error message is: ' + error_message()

                        EXEC msdb.dbo.sp_send_dbmail
                        @profile_name = 'Internal mail',
                        @recipients = @recp,
                        @body = @error_msg ,
                        @subject = 'Error occurred on reporting server' ;
            END CATCH       

            --add the schedule
            set @schedulestr = @groupAfull + '_Schedule'
            set @sql = 'if not exists (select * from [' + @ServerName + '].msdb.dbo.sysschedules where name = ''' + @schedulestr + ''')
            BEGIN
            EXEC [' + @ServerName + '].msdb.dbo.sp_add_schedule
            @schedule_name = ''' + @schedulestr + ''', 
            @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=20140819, 
            @active_end_date=99991231, 
            @active_start_time= '+ @defaultscheduleStart +', 
            @active_end_time=235959
            end
            '
            --print @sql
            begin try
                        EXEC sp_executeSQL @sql

            END TRY
            BEGIN CATCH
                        print 'error message is:'
                        print error_message()
                        set @error_msg='Error occurred when executing CreatBackupJob against server ' + @ServerName + '. Failed to create new job schedule '+ @groupAfull +'. The error message is: ' + error_message()

                        EXEC msdb.dbo.sp_send_dbmail
                        @profile_name = 'Internal mail',
                        @recipients = @recp,
                        @body = @error_msg ,
                        @subject = 'Error occurred on reporting server' ;
            END CATCH       

            --attach the schedule
            set @sql = 'EXEC [' + @ServerName + '].msdb.dbo.sp_attach_schedule
            @job_name = ''' + @groupAfull + ''',
            @schedule_name = ''' + @schedulestr + '''   
            '
            begin try
                        EXEC sp_executeSQL @sql

            END TRY
            BEGIN CATCH
                        print 'error message is:'
                        print error_message()
                        set @error_msg='Error occurred when executing CreatBackupJob against server ' + @ServerName + '. Failed to attach the schedule to the job '+ @groupAfull +'. The error message is: ' + error_message()

                        EXEC msdb.dbo.sp_send_dbmail
                        @profile_name = 'Internal mail',
                        @recipients = @recp,
                        @body = @error_msg ,
                        @subject = 'Error occurred on reporting server' ;
            END CATCH       
end 
GO

The job created is created with 3 retries, so if backup fails it retires and then backs up all the database again irrespective where it failed. I am trying to change the procedure so that when the job fails it runs backup for databases which have failed. I have written below code to replace where it backups up database.

*@lastbackuptime  is declared 

BEGIN
    SET @lastbackuptime = (select max(backup_finish_date) from msdb.dbo.backupset where database_name=@dbname and type=''''D'''')
    if (Select datediff(hour,@lastbackuptime,getdate()))> 18
        Begin
            SET @fileName = @path + replace(@@SERVERNAME, ''''\'''', ''''-'''') + ''''_'''' + @dbname + ''''_'''' + @fileDate + '''''+ @gstr + ''''' +  ''''.BAK''''  
            BACKUP DATABASE @dbname TO DISK = @fileName   
        End
    else
        Begin
        Print ''''Backup exists for database ''''
        End  
    FETCH NEXT FROM db_cursor INTO @dbname   
END 

When I make this changes from job step , query parses fine and job runs fine. But when i put this in this procedure and try to create jobs remotely it fails with syntax error. As it is dynamic sql it does not provide me any clue where is the syntax error.

Would be great if someone can spot it.

Regards, Sid

Alex
  • 4,885
  • 3
  • 19
  • 39
Sid
  • 33
  • 6
  • 3
    When debugging dynamic SQL the key it to print the resultant string before executing it. Long strings can be truncated by PRINT command, so you need to use workarounds: http://stackoverflow.com/questions/7850477/how-to-print-varcharmax-using-print-statement . Once you get your complete string printed check it for syntax errors and run it. Fix errors in dynamic SQL generator code and rinse and repeat. – Alex Jun 08 '16 at 06:39

1 Answers1

1

Questions of "Dynamic SQL not working" type come up often and sometimes (as in your case) can be very hard to answer, as we cannot execute the code you have supplied.

So this answer describes the method of debugging dynamic SQL.

  1. Add a PRINT statement to print your dynamic SQL string just before it gets executed. When using PRINT command long strings can be truncated, so you need to use workarounds (How to print VARCHAR(MAX) using Print Statement? provides a few solutions).

  2. Once you get your complete string printed check it for syntax errors and/or just run it. You can check code for syntax error by pasting it into a new query window and running "Parse" (Ctrl+F5).

  3. Once the error in dynamic SQL is identified you need to find the place in your dynamic SQL generator code that produced this error and fix it there.

  4. Repeat this process until no errors left.

Community
  • 1
  • 1
Alex
  • 4,885
  • 3
  • 19
  • 39