Thursday, July 16, 2009

Automating the Transfer of Dynamics GP Databases – Part 2

In Part 1 we wrote a SQL script that will create a backup script for all of the Dynamics GP databases to help us automate the transfer all of Dynamics GP’s databases to another SQL server or instance.  In Part 2 we’ll do the same for restoring the databases.

First, let’s review the T-SQL for restoring a database.  In this case the DYNAMICS database.

RESTORE DATABASE DYNAMICS
    FROM DISK = 'E:\SQLBackups2\DYNAMICSTransfer.bak'
    WITH RECOVERY,
        MOVE 'GPSDYNAMICSDat.mdf' TO 'E:\SQLData2\GPSDYNAMICSDat.mdf',
        MOVE 'GPSDYNAMICSLog.ldf' TO 'E:\SQLData2\GPSDYNAMICSLog.ldf'

Not too bad but there’s a bit of a kink here.  The MOVE clauses put the the database files in the desired location on the destination instance.  If you're putting the files in the exact same folder structure on the destination as the source then we’re all set and can skip the MOVE clauses and build our RESTORE statements the same way as the backup script as follows:

--  Setting NOCOUNT on prevents the row count message from appearing at the bottom.
--  Note that the SQL statements do not include column headers.  This makes
--  the entire output of the results window executable.

SET NOCOUNT ON

DECLARE @BackupFolder nvarchar(max)
SET @BackupFolder = 'E:\SQLBackups2\'

--  Create statement to backup DYNAMICS database
SELECT
        '
        RESTORE DATABASE DYNAMICS
            FROM DISK = ''' + @BackupFolder + 'DYNAMICSTransfer.bak''
            WITH RECOVERY
        GO
        '

UNION ALL

--  Create statements to backup company databases.
SELECT
        REPLACE(
        '
        RESTORE DATABASE {COMPANYDB}
            FROM DISK = ''' + @BackupFolder + '{COMPANYDB}Transfer.bak''
            WITH RECOVERY
        GO
        ',
        '{COMPANYDB}', LTRIM(RTRIM(INTERID)))
    FROM DYNAMICS.dbo.SY01500

 

If, as in many cases, your folder structure is different on the machine you’re transferring to, you need to deal with the MOVE clause.  To do this we need to know how many files there are and their logical file names. 

If we were dealing strictly with Dynamics GP Utilities created databases with the default files we could easily just hardcode the two MOVE clauses and insert the company ID in the appropriate places.  However, if you’ve added files to a database (such as spanning it over multiple volumes), or restored one company database over another (such as restoring a production database over a training one for training purposes), your logical file names will not be standard.

The database file information is most easily extracted from the master.sys.master_files system view in the master database.  Once we have the list of files we can build a statement that creates a restore script as follows:

--  Setting NOCOUNT on prevents the row count message from appearing at the bottom.
--  Note that the SQL statements do not include column headers.  This makes
--  the entire output of the results window executable.

SET NOCOUNT ON;

--Declare and set variables for the restore folders
DECLARE @BackupFolder nvarchar(max);
DECLARE @NewDataFolder nvarchar(max);
DECLARE @NewLogsFolder nvarchar(max);

SET @BackupFolder = 'E:\SQLBackups2\';
SET @NewDataFolder = 'E:\SQLData2\';
SET @NewLogsFolder = 'E:\SQLData2\';

--  This common table expression returns the list of Dynamics
--  databases, identifying the min and max file IDs.
WITH Databases
AS
(
    SELECT
            DatabaseID = Databases.database_id
            ,DatabaseName = Databases.name
            ,MinFileID = MIN(DatabaseFiles.file_id)
            ,MaxFileID = MAX(DatabaseFiles.file_id)
        FROM master.sys.databases AS Databases
            INNER JOIN master.sys.master_files AS DatabaseFiles
                ON Databases.database_id = DatabaseFiles.database_id
        WHERE Databases.name = 'DYNAMICS'
            OR Databases.name IN (
                SELECT
                        INTERID
                    FROM DYNAMICS.dbo.SY01500)
        GROUP BY
            Databases.database_id
            ,Databases.name
),

--  This common table expression returns the list of files associated
--  with the Dynamics databases, identifying the first and last files
--  in the list.
DatabaseFiles
AS
(
    SELECT
            DatabaseName = RTRIM(LTRIM(Databases.DatabaseName))
            ,LogicalFileName = DatabaseFiles.name
            ,PhysicalFileName =
                CASE
                    WHEN DatabaseFiles.file_id > 2 THEN
                        CASE
                            WHEN type_desc = 'ROWS' THEN 'GPS'
                                + RTRIM(LTRIM(Databases.DatabaseName))
                                + 'Dat_' + CAST(DatabaseFiles.file_id AS nvarchar(3)) + '.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)
                            ELSE 'GPS' + RTRIM(LTRIM(Databases.DatabaseName)) + 'Log_'
                                + CAST(DatabaseFiles.file_id AS nvarchar(3)) + '.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)
                        END
                    ELSE
                        CASE
                            WHEN type_desc = 'ROWS' THEN 'GPS'
                                + RTRIM(LTRIM(Databases.DatabaseName)) + 'Dat.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)
                            ELSE 'GPS'
                                + RTRIM(LTRIM(Databases.DatabaseName)) + 'Log.' + SUBSTRING(physical_name, (LEN(physical_name) - 2), 3)
                        END
                    END
            ,FileType = type_desc
            ,FileID = DatabaseFiles.file_id
            ,IsFirstFile =
                CASE
                    WHEN DatabaseFiles.file_id = Databases.MinFileID THEN 1
                    ELSE 0
                END
            ,IsLastFile =
                CASE
                    WHEN DatabaseFiles.file_id = Databases.MaxFileID THEN 1
                    ELSE 0
                END
        FROM Databases
            INNER JOIN master.sys.master_files AS DatabaseFiles
                ON Databases.DatabaseID = DatabaseFiles.database_id
)

--  The SELECT statement outputs the appropriate parts of the
--  RESTORE statement depending on whether this the first, last
--  or a middle file.
SELECT
        CASE
            WHEN IsFirstFile = 1 THEN
'
RESTORE DATABASE ' + DatabaseName + '
    FROM DISK = ''' + @BackupFolder + DatabaseName + 'Transfer.bak''
    WITH RECOVERY,
        MOVE ''' + LogicalFileName + ''' TO ''' + @NewDataFolder + PhysicalFileName + ''',
'
            WHEN IsLastFile = 1 THEN
'        MOVE ''' + LogicalFileName + ''' TO ''' + @NewDataFolder + PhysicalFileName + '''
GO
'
            ELSE
'        MOVE ''' + LogicalFileName + ''' TO ''' + @NewDataFolder + PhysicalFileName + ''',
'
        END
    FROM DatabaseFiles
    ORDER BY
        DatabaseName,
        FileID

So this is a little involved and is best read by copying into a query window but essentially we’re doing the following:

  • The first common table expression, Databases, returns a row for each Dynamics database and includes the minimum and maximum file IDs.  This will allow us to know what the first and last files are for a particular database and thus know when to prepend the RESTORE clause at the beginning and the GO statement at the end.
  • The second common table expression, DatabaseFiles, returns the list of database files with their logical names and new physical locations.  Additionally, it indicates whether a particular file is the first or last one in the list.
  • The final SELECT statement actually builds the RESTORE statement using the DatabaseFiles common table expression.

Using my two company test environment, you get the following restore script:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

RESTORE DATABASE DYNAMICS
    FROM DISK = 'E:\SQLBackups2\DYNAMICSTransfer.bak'
    WITH RECOVERY,
        MOVE 'GPSDYNAMICSDat.mdf' TO 'E:\SQLData2\GPSDYNAMICSDat.mdf',
        MOVE 'GPSDYNAMICSLog.ldf' TO 'E:\SQLData2\GPSDYNAMICSLog.ldf'
GO

RESTORE DATABASE FOUR
    FROM DISK = 'E:\SQLBackups2\FOURTransfer.bak'
    WITH RECOVERY,
        MOVE 'GPSFOURDat.mdf' TO 'E:\SQLData2\GPSFOURDat.mdf',
        MOVE 'GPSFOURLog.ldf' TO 'E:\SQLData2\GPSFOURLog.ldf',
        MOVE 'FOURDat_2' TO 'E:\SQLData2\GPSFOURDat_3.ndf'
GO

RESTORE DATABASE TWO
    FROM DISK = 'E:\SQLBackups2\TWOTransfer.bak'
    WITH RECOVERY,
        MOVE 'GPSTWODat.mdf' TO 'E:\SQLData2\GPSTWODat.mdf',
        MOVE 'GPSTWOLog.ldf' TO 'E:\SQLData2\GPSTWOLog.ldf'
GO

Note that the FOUR company database has had it’s additional data file properly listed in the MOVE clauses.

If we now run this script against our second server (or server instance), we’ll restore all of the databases we backed up previously.

In our Part 3 we’ll look at how to reattach logins to database users.

No comments:

Post a Comment