September 2006 - Posts

Restoring a Database Backup to a new Database using SMO

In the MSDN SMO forum yesterday someone asked how to restore a backup to a new database without overwriting the existing database. The documentation isn't abundantly clear as to how to go about this so I created a small demo of how to do this.

First we need to connect to the server, then create a backup device object and set its properties.

        Dim srv As Server
        Dim srvConn As ServerConnection
        srv = New Server("MyServer")
        srvConn = srv.ConnectionContext
        srvConn.LoginSecure = True

        Dim bdi As BackupDeviceItem
        bdi = New BackupDeviceItem("AdventureWorks_backup_200609210229.bak", DeviceType.File)

Next, we need to define the Restore object, set properties for Recovery, add the backup device, and set the name of the recovered database.

        'Define a Restore object variable.
        Dim rs As Restore
        rs = New Restore
        'Set the NoRecovery property to False.
        rs.NoRecovery = False
        'Add the device that contains the full database backup to the Restore object.
        rs.Devices.Add(bdi)
        'Specify the database name.
        rs.Database = "AdventureWorksPrime"

Now, to place the database files into newly named files SMO uses an object called a RelocateFile object, and the Restore process uses an ArrayList of these objects to define where to place these files during restore. The LogicalFileName property must contain the database logical file name from the source database, and the PhysicalFileName property can be set to the desired location and file name for the new database file. Here I've used a separate RelocateFile object variable for the Data and Log files in the database.

        'Define the RelocateFile object variables
        Dim alRSFile As New RelocateFile
        Dim alRSLog As New RelocateFile
        'Set the FileName property appropriately
        alRSFile.LogicalFileName = "AdventureWorks_Data"
        alRSFile.PhysicalFileName = "d:\MSSQL\Data\AdWorks.mdf"
        alRSLog.LogicalFileName = "AdventureWorks_Log"
        alRSLog.PhysicalFileName = "d:\MSSQL\Data\AdWorks.ldf"
        'Add the RelocateFile objects to the Restore RelocateFiles collection
        rs.RelocateFiles.Add(alRSFile)
        rs.RelocateFiles.Add(alRSLog)
        'Restore the full database backup
        rs.SqlRestore(srv)

This allows you to build an SMO application to build duplicate databases in other locations for whatever reason you may have.

Allen

That Evil --Cannot generate SSPI context-- Message

When I was upgrading my servers to SQL Server 2005 I ran into some problems with permissions, so I decided to focus on one problem at a time, and just upgraded using the admin account I've been using for some time. I know that running SQL Server under an admin account is not a good idea, so I've been working on resetting my servers to run under a domain user account. I modified a set of instances on one development server to use the user account, but was unable to access those servers from my workstation, and got the error message "Cannot generate SSPI context".

The Microsoft Support Site helped me understand the problem, but the site that really turned the corner for me was How to use Kerberos authentication in SQL Server. On that page I learned about the Server Principle Name, and how it has to be set for the execution account for each instance of SQL Server it's running, to allow a client to successfully pass Kerberos tokens between systems, and that's how integrated authentication is handled most efficiently.

First, I used the setspn utility to determine whether the account was registered with the SPN (Server Principle Name) for each SQL Server instance running.

setspn -L sqlacct

I saw that no servers were returned from that command, which was probably why I was getting the error. I then registered the account properly.

setspn -A MSSQLSvc/MySQLServer:1202 sqlacct 
setspn -A MSSQLSvc/MySQLServer:1132 sqlacct 
setspn -A MSSQLSvc/MySQLServer:1155 sqlacct 

Once this was done I was able to connect to those servers, and life was good again. Now to schedule some off hours time to make those changes in my production environment.

Allen

Using OUTPUT in Transact-SQL to maintain referential integrity

Note: Read Hugo's comments below, and follow the links to Conor Cunningham's blog. This solution will not yield consistent results. It happened to work for me, but a cursor is the only reliable way to solve this problem. AMW

A user asked me today to move data associated with an application from development to our production database. This involved copying data associated with an entity and housed in a parent-child set of tables. The problem was that the tables used identity columns for their keys, and the specific key values in the development database were already in use by other entities in the production database. Add to that the complication that the child database reference was ONLY to the identity key in the parent table. I needed to copy this data, generating new identity values, but maintain the relationship between parent and child that existed in the development database.

This scenario cries out "cursor", but I try to find set-based methods whenever possible. My first pass was to use the new OUTPUT clause on my insert query, storing the old ID and the new ID in the output table variable. I ran into a problem, though, in that SQL Server will only put data in the output table that is being inserted into the destination table, so the old ID isn't available. I still needed to build some way to cross-reference the two. Here's my solution, you decide whether it works for you.

First I declared two table variables.

declare @oldValues table (seq int identity, ID int)
declare @newValues table (seq int identity, ID int)

Next, I selected the ID values from the original table, sorting on the ID value and letting the identity column in my table variable generate a sequence number starting with one and incrementing by one.

INSERT INTO @oldValues
SELECT [ID]
FROM [DevDB].[dbo].[EntityParent] where EntityID = 137
ORDER BY ID

Then, I perform the insert from the DevDB into the ProdDB (no, I don't really have them on the same server, but it makes the concept clearer.) I use the OUTPUT clause in the insert statement to retain the new ID values in the table variable I declared earlier.

INSERT INTO [ProdDB].[dbo].[EntityParent]
           ([EntityID],[ColA],[ColB],[ColC])
OUTPUT INSERTED.[ID] as ID INTO @newValues
SELECT [EntityID],[ColA],[ColB],[ColC]
  FROM [DevDB].[dbo].[EntityParent] where EntityID = 137
ORDER BY ID

Now, I've got the old ID values in @oldValues and the new ID values in @newValues, with the "seq" column the linking value between them and can now copy the child table data using the joined tables.

INSERT INTO [ProdDB].[dbo].[EntityChild]
           ([ParentID],[ChildA],[ChildB],[ChildC])
SELECT n.[ID] as ParentID,c.[ChildA],c.[ChildB],c.[ChildC]
FROM [DevDB].[dbo].[EntityChild] c
INNER JOIN @oldValues o
ON c.[ParentID] = o.[ID]
INNER JOIN @newValues n
ON o.[seq] = n.[seq]

Allen

User Group Presentation Feedback

Last Thursday I had the opportunity to "practice" the presentation I'm giving at PASS in Seattle in front of the Ohio North SQL Server Special Interest Group (ONSQL SIG).

This was my third presentation for the group, but the first that was based on my actual work with the SQL Server 2005 tools (as opposed to the mostly slide-ware I used in the first two presentations). I know that it's important to give a dynamic and unique presentation at PASS, and I worked towards that goal in building this presentation.

As I presented various examples of code I've written to manage my servers I noticed a number of people who's face showed that "light bulb" going on, and then writing down notes.

I really appreciate the group allowing me to hone my presentation skills, and felt great whenever I'd catch those moments in the audience.

Allen

Cleaning up Empty Schemas after Upgrading to SQL Server 2005 - Followup

After writing the code I posted yesterday I ran it against my development server, then the QA servers, checking applications to ensure they ran fine after I performed the cleanup. Once I was satisfied that the cleanup was non-invasive, I ran the program against my production servers.

Well, everything went well except one application which is written in Cold-Fusion and uses JDBC connections to SQL Server. Almost immediately after I ran the cleanup program I received emails that all the web sites using this application were down. The error message they received was:

Error Executing Database Query.   
 
[Macromedia][SQLServer JDBC Driver][SQLServer]Could not find prepared statement with handle 1.

I went into the Java Runtime Console and tested the connection to a half-dozen of the databases and verified that JDBC could successfully connect, but the web sites were still down. I "punted" and rebooted the web servers running the application and as soon as they came back up the web sites were all running again.

Now, this change really should have been transparent - I deleted schemas that owned no objects, but for some reason Cold Fusion reacted badly. What's interesting was that the same problem did not occur in my QA environment for the same application, so the outage was completely unexpected.

My point for this post is that you have to be prepared when doing any cleanup for unexpected consequences.

Allen

Cleaning up Empty Schemas after Upgrading to SQL Server 2005

One of the problems I've run into after upgrading from SQL Server 2000 to SQL Server 2005 has been the "empty" schemas the upgrade process creates. After the upgrade, in each database, I've found a schema for every defined user and every defined database role, even the system-defined roles! To clean these up I've created a console application using SMO.

First, I define a class to hold the names of the schemas which qualify to be dropped.

Public Class dbSchema
    Public name As String
    Sub New(ByVal newName As String)
        name = newName
    End Sub
End Class

Next, in the Main subroutine, I defined the objects I'll need to do the cleanup. The first variable is an array of type Urn, which is what SMO returns from the EnumOwnedObjects method of the Schema object. It's this method that makes this code work so nicely.

        Dim arrURN() As Urn
        Dim colSchema As SchemaCollection
        Dim objSchema As Schema
        Dim colDatabase As DatabaseCollection
        Dim objDatabase As Database
        Dim colDropSchema As New Collection
        Dim strSchemaName As String
        Dim objDropSchema As dbSchema

Next we have to connect to our target server.

        'Connect to the server
        Dim srv As Server
        Dim srvConn As ServerConnection
        srv = New Server("MyServer")
        srvConn = srv.ConnectionContext
        srvConn.LoginSecure = True

Now comes the fun part. I loop through the database collection on the server, and then the schema collection within each database. You have to leave the 'sys', 'guest', 'dbo' and 'INFORMATION_SCHEMA' schemas in place, because SQL Server gets upset if you try to remove them, so after making sure we're not looking at one of those we call the EnumOwnedObjects method for the schema. If the Length property of the resultant array is 0, then the schema doesn't own any objects and can be safely removed, so I add it to the collection of schema names to be dropped. Once I loop through the list of schemas, I loop through the list of delete candidates, and drop each one.

        colDatabase = srv.Databases                 'Get the server's database collection
        For Each objDatabase In colDatabase
            colSchema = objDatabase.Schemas         'Get the Schema collection
            colDropSchema.Clear()                   'Init the list of empty schema names
            For Each objSchema In colSchema
                ' Leave the 'sys', 'guest', 'dbo' and 'INFORMATION_SCHEMA' schemas in place
                If Not (objSchema.Name = "sys" Or objSchema.Name = "guest" _
                      Or objSchema.Name = "dbo" Or objSchema.Name = "INFORMATION_SCHEMA") Then

                    arrURN = objSchema.EnumOwnedObjects 'Determine if the schema owns any objects
                    If arrURN.Length = 0 Then           'If not, then add the schema name to the drop list
                        strSchemaName = objSchema.Name
                        If Not (colDropSchema.Contains(strSchemaName)) Then
                            colDropSchema.Add(New dbSchema(strSchemaName), strSchemaName)
                        End If
                    End If
                End If
            Next
            For Each objDropSchema In colDropSchema             'Loop through the drop list
                objSchema = colSchema.Item(objDropSchema.name)  'Attach the schema to be dropped
                objSchema.Drop()                                'Drop the schema
            Next
        Next

Remember that this code must be run against SQL Server 2005 databases only, so you might want to test the compatibility level to be equal to 90 before going forward, but on my target servers all the databases have been upgraded.

Allen

Getting Ready for PASS

Well, I've finished the presentation for the 2006 PASS Community Summit. My presentation is called DBA-201: Using SMO to create custom SQL Server Management solutions and will be on 11/15/2006 from 1:30 PM - 2:45 PM. I did a practice presentation at work last week, and got very positive comments. I'll be giving the presentation again next Thursday night at the meeting of the Ohio North SQL Server Users Group.

Allen