May 2006 - Posts

Accessing SAC after Cluster Upgrade to SQL 2005

I ran into a problem after upgrading production servers running under Microsoft Cluster Server, Windows Enterprise Server 2003 SP1, and SQL Server 2005 SP1 Enterprise Edition.

Once I completed the upgrade and applied the patch, I needed to examine the values set in the Features component of the Surface Area Configuration tool. The tool attempts to connect to localhost\instancename, but reports the following error:

    An error has occurred while establishing a connection to the server.
    When connecting to SQL Server 2005, this failure may be caused by the fact
    that under the default settings SQL Server does not allow remote connections.
    (provider: SQL Network Interfaces, error:26 - Error Locating Server/Instance
    Specified)(Microsoft SQL Server)

I'm logged into the virtual server via Remote Desktop, but the SAC tool thinks I'm logged into the physical server supporting the virtual server. I seem to be caught between a rock and a hard place here, as I can't enable remote connections without first gaining access on the server itself.

Well, after a lucky break I found the answer.

sp_configure 'remote admin connections', 1
go
reconfigure
go

Once this was executed on the server I was able to go into Surface Area Configuration, enter the virtual server name, and connect successfully and make the other changes necessary. A simple solution to a scary problem. (Even though I'd used sp_configure for years on both Sybase and MSSQL servers it's easy to forget about the simple solutions.)

Allen

RANK and processing Marathon Results

In my presentation on May 11 to the Ohio North SQL Server Special Interest Group (you can download my presentation and demo code from this site) I covered Transact-SQL enhancements in SQL Server 2005. I flew through some of the demos, partially due to time, but also partially because I haven't had a chance to really work with some of the features and I am unfamiliar with them. One such feature was the RANK function, and I used the example from Books Online. The example is not very revealing, so I decided to play a bit on my own.

Last weekend I ran the Fargo Marathon (and for a number of reasons managed to tie my Personal Worst). The event, however, made me think about the data possibilities and the rank function. Here's a sample of the results raw data from the race:

LastNameFirstNameSexAgeTimeDIVCityStateCountry
PoteetScottM3203:11:08M30-34 N Las Vegas, NV, USA
PoteetKristinF2903:34:43F25-29 N Las Vegas, NV, USA
JacksonDavidM4203:39:25M40-44 W Lafayette, IN, USA
RossLynnetteF4105:26:54F40-44 Mc Intosh
MoenBrandonM2202:37:44M20-24 Mankato, MN, USA
KennedyJamesM4102:59:07M40-44 Eagan, MN, USA
SteffensMontyM4703:02:21M45-49 Sartell, MN, USA
FallerGerryM4003:03:28M40-44 Maple Grove, MN, USA
WeluMikeM4003:03:53M40-44 Canistota, SD, USA
GrindallMegF2503:04:45F25-29 Fargo, ND, USA

I built a query to present the results ranking the finishers by their time within their Division like this:

select LastName, FirstName, Sex, Age, CONVERT(char(8), [Time], 108) as Time, DIV, 
  RANK() over (PARTITION by DIV order by [TIME]) as Place
FROM dbo.OverallResults
ORDER by DIV, [TIME]

The PARTITION clause tells the RANK function where to draw the lines in the ranking process, so by partitioning by division gives me the age group separation, then ordering by Time within the Division gives me the top finishers within each division. This process used to be quite complicated, and the new RANK function makes it a relatively simple process.

LastNameFirstNameSexAgeTimeDIVPlace
RebenitschSarahF1803:46:09F15-19 1
PedersonElizabethF1903:55:10F15-19 2
StepkaRachelF1904:15:01F15-19 3
MuellerCarrieF2203:15:36F20-24 1
BotnenAmyF2103:21:59F20-24 2
ElsethMandyF2403:22:51F20-24 3
GrindallMegF2503:04:45F25-29 1
KasparErinF2603:09:46F25-29 2
FieldJillF2603:13:03F25-29 3
CookTerriF3003:08:33F30-34 1
SawtelleMindyF3303:08:34F30-34 2
ReichLisaF3103:27:51F30-34 3
BlatherwickJossM1903:20:57M15-19 1
BaxterCodyM1903:27:38M15-19 2
NelsonJacobM1803:33:43M15-19 3
MoenBrandonM2202:37:44M20-24 1
NielsenAndyM2302:59:10M20-24 2
RogersNickM2103:06:58M20-24 3
WallinChadM2602:31:49M25-29 1
MillerShawnM2602:32:33M25-29 2
YurekJacobM2502:59:33M25-29 3
WestPatrickM3302:45:59M30-34 1
Grafenstein-KinzMicahM3302:53:28M30-34 2
ZiegenfussJoeM3102:59:14M30-34 3
WellsMikeM3902:56:43M35-39 1
RobinJimM3802:57:15M35-39 2
SchatkowskyCraigM3703:02:16M35-39 3

No, you won't find my result in there - it wasn't a good day for me, but if you're trying to quickly do the results for the race management group you can see how the new feature makes life easier.

Allen

Attention to Detail when using SMO

In a my post Execute SQL Scripts against multiple DB's with SMO I demonstrated a way to execute a script against multiple databases. I then created snapshots on all those databases and found a new "bug" in my script processing code.

Duh! You can't alter a Snapshot database! Of course I knew that, but I didn't make provisions in my multi-database script processing code to skip snapshot databases. Here's the updated code:

        ' Browse the database collection on the target server
        Dim lisDBs As DatabaseCollection
        lisDBs = srvMgmtServer.Databases
        Dim objDB As Database
        For Each objDB In lisDBs
            If Not objDB.IsDatabaseSnapshot Then
                If Left(objDB.Name, 4) = "xxx_" Or Left(objDB.Name, 4) = "yyy_" Then
                    objDB.ExecuteNonQuery(allText)
                End If
            End If
        Next

Notice the check against the IsDatabaseSnapshot property before doing any work against that database. These are the kind of features I really enjoy while working with SMO.

Allen

Blending SMO and Transact-SQL to create solutions

I keep finding that I need to mix Transact-SQL with my SMO code to get things done. I have a server where we keep databases for a lot of different web sites. The application uses database data to configure and present information on each web site, dependent on design data in the database, so using one application we can host many very different web sites. When the application changes I have to change all of the databases with the same changes, which prompted yesterday's blog post.

Problems occur with these sites on occasion when the people who design the sites inadvertently delete or significantly modify the site data. I get a call asking me to restore the data to its previous state. Prior to upgrading to SQL 2005 I always restored the database to a separate database and allowed the users to copy back the data they'd lost to the production database, bringing them back to the point they were before making their mistake.

SQL Server 2005 has a great new feature called Database Snapshot, which allows me to make a point-in-time image of the database, accessible as a read-only database, as it existed when the snapshot was taken. It uses NTFS magic to keep the actual disk space at a minimum, only taking space for those pages in the database that have actually changed in the base database since the snapshot was taken. I decided to take advantage of this feature to allow the users to get their data back without my having to perform a restore.

Architecturally, I decided that it'd be best to have a week's worth of these snapshots available, in the event the user realized that they deleted the data on Friday that they now critically need on Monday. Based on this decision I appended the string "_snapshot_" and the first three letters of the day of the week to the end of the database name.

Because I like SMO to automate processes, I created a console app to build the snapshots. First, I need to drop the databases from last week's job:

        Dim lisDBs As DatabaseCollection
        lisDBs = srvMgmtServer.Databases
        Dim objDB As Database
        strSQL = ""
        For Each objDB In lisDBs                    ' Drop the snapshots with today's day name
            If Right(objDB.Name, 13) = "_snapshot_" + strWeekDay Then
                strSQL = strSQL + "DROP DATABASE " + objDB.Name + "; "
            End If
        Next
        objDB = srvMgmtServer.Databases("master")
        objDB.ExecuteNonQuery(strSQL)

I'd first attempted to use the SMO Drop() method, but kept getting errors because I'd changed the collection while looping through it. (I'm mostly a DBA, and there's probably a way to use the Drop() method, but this accomplishes the task at hand.)

The Database object in SMO has read-only properties to tell you if a given database is a snapshot or not, but I didn't find anything to allow me to set the properties before creating the database so I returned to Transact-SQL to create the database. Because snapshots are based on the file system features you need to specify each file used by the database (not the log, because the snapshot is read-only), so you need to loop through the FileGroups collection, evaluate each Filegroup to find the DataFileCollection, then loop through that to find each data file. The DataFile.Name property contains the logical name, while the DataFile.FileName property contains the physical file name. I lop off the extension from the base database filename, add the day of the week, then the extension ".snap" so anyone browsing the file system can know what the file is.

        For Each objDB In lisDBs                    ' Loop through the databases on the server
            If Left(objDB.Name, 4) = "xxx_" Or Left(objDB.Name, 4) = "yyy_" Then
                ' Create a snapshot of the database
                Dim objFileGroups As FileGroupCollection
                Dim objFileGroup As FileGroup
                Dim intFileCtr As Integer
                Dim strSnapDBName As String

                strSnapDBName = objDB.Name + "_snapshot_" + strWeekDay
                If Not objDB.IsDatabaseSnapshot Then
                    strSQL = "CREATE DATABASE " + strSnapDBName + " ON "
                    intFileCtr = 0
                    objFileGroups = objDB.FileGroups
                    For Each objFileGroup In objFileGroups
                        Dim objDataFiles As DataFileCollection
                        Dim objDataFile As DataFile
                        objDataFiles = objFileGroup.Files
                        For Each objDataFile In objDataFiles
                            Dim strFileName As String

                            strFileName = Left(objDataFile.FileName, Len(objDataFile.FileName) - 4)
                            If intFileCtr > 0 Then
                                strSQL = strSQL + ", "
                            End If
                            intFileCtr += 1
                            strSQL = strSQL + "( NAME = " + objDataFile.Name + ", FILENAME = '" + strFileName + strWeekDay + ".snap')"
                        Next
                    Next
                    strSQL = strSQL + " AS SNAPSHOT OF " + objDB.Name
                    objDB.ExecuteNonQuery(strSQL)
                End If
            End If
        Next

The net of this code is that for each base database it creates Transact-SQL that looks like this:

CREATE DATABASE xxx_sales_snapshot_Wed ON
( NAME = SPri1_dat, FILENAME = 
'C:\Program Files\Microsoft SQL Server\mssql.1\mssql\data\SPri1datWed.snap'),
( NAME = SPri2_dat, FILENAME = 
'C:\Program Files\Microsoft SQL Server\mssql.1\mssql\data\SPri2dtWed.snap'),
( NAME = SGrp1Fi1_dat, FILENAME = 
'C:\Program Files\Microsoft SQL Server\mssql.1\mssql\data\SG1Fi1dtWed.snap'),
( NAME = SGrp1Fi2_dat, FILENAME = 
'C:\Program Files\Microsoft SQL Server\mssql.1\mssql\data\SG1Fi2dtWed.snap'),
( NAME = SGrp2Fi1_dat, FILENAME = 
'C:\Program Files\Microsoft SQL Server\mssql.1\mssql\data\SG2Fi1dtWed.snap'),
( NAME = SGrp2Fi2_dat, FILENAME = 
'C:\Program Files\Microsoft SQL Server\mssql.1\mssql\data\SG2Fi2dtWed.snap')
AS SNAPSHOT OF xxx_sales

Once it creates the SQL script it uses the ExecuteNonQuery() method to create the snapshot. I'm not sure how elegant this solution is, but it certainly works and should make my user community happier.

Allen

Execute SQL Scripts against multiple DB's with SMO

In a previous post (Using SMO to set Database Properties) I covered building a structure for a utility program using SMO. Today I needed to run a SQL script sent by one of the developers at my company against over 100 databases with similar structures.

First, I pull the script into a string field in one quick read operation:

        Dim allText As String

        Using FileReader As New _
        Microsoft.VisualBasic.FileIO.TextFieldParser("C:\script.sql")
            allText = FileReader.ReadToEnd
        End Using

We support two different company's databases with this application, and separate the databases via a three letter code at the beginning of each database name. Other databases on that server don't follow that convention, so it's easy to use those codes to select just the databases I want the changes made to. The core code is as follows:

        ' Browse the database collection on the target server
        ' Browse the database collection on the target server
        Dim lisDBs As DatabaseCollection
        lisDBs = srvMgmtServer.Databases
        Dim objDB As Database
        For Each objDB In lisDBs
            If Left(objDB.Name, 4) = "xxx_" Or Left(objDB.Name, 4) = "yyy_" Then
                objDB.ExecuteNonQuery(allText)
            End If
        Next

This sure beats switching from one database to the next in Management Studio and executing the script over 100 times!

Allen

New Features in Transact-SQL

Just a reminder that tomorrow evening (May 11, 2006) I'll be presenting to the Ohio North SQL Server User's Group on the new features in Transact-SQL, at the Cleveland Microsoft Office. This session will be simulcast to the Columbus and Detroit User Groups as well, as I understand it. Details are at this URL: http://www.bennettadelson.com/technicalresources/sqlsig.aspx

Allen

Using SMO to set Database Properties

In my work environment we've got a number of production servers, each containing multiple databases from different applications, both internally developed and vendor supplied. Whenever we need to test changes to these apps I move a backup file from a production database to a development or QA server and restore the database there, allowing people to use real data to ensure the quality of their work. I do full backups on our production databases once a day, and transaction log backups once an hour (or more frequently, as needed). In the Dev/QA area, though, I don't need the transaction log backups, so I set the databases to Simple Recovery Mode.

It's easy to forget to change this setting on a busy day and then the drive allocated for the log segments fills up and people get mad because their testing stops working and blah, blah, blah. You know the drill.

I've found the quickest way to ensure I don't run into these problems is a quick SMO program to set the database properties of all databases on the server, in case I missed one along the way. I created a VB.Net console app to do this, and it's really quite straightforward.

The first thing you’ll need to do is to select Add Reference from the Project menu. Within the Add Reference dialog, use the Ctrl key to select the following assemblies (you may have to browse to the C:\Program Files\Microsoft SQL Server\90\SDK\Assemblies folder):

Microsoft.SqlServer.ConnectionInfo.dll
Microsoft.SqlServer.Smo.dll
Microsoft.SqlServer.SmoEnum.dll
Microsoft.SqlServer.SqlEnum.dll

Next, open up the code window for the application, and at the top of the code, before any declarations, insert the following lines:

Imports Microsoft.SqlServer.Management.SMO
Imports Microsoft.SqlServer.Management.Common

Now you'll have a Sub Main() and End Sub pair in the code window. After Sub Main, define your server like this:

        ' Connect to the server
        Dim srvMgmtServer As Server
        srvMgmtServer = New Server("MyDevServer")
        Dim srvConn As ServerConnection
        srvConn = srvMgmtServer.ConnectionContext
        srvConn.LoginSecure = True
        If srvConn.LoginSecure = False Then
            srvConn.Login = "SqlLogin"
            srvConn.Password = "xxxxxxxx"
        End If

This connects to the server using Windows Authentication if LoginSecure is set to True, and uses the supplied Login and Password values if LoginSecure is set to False.

Next is the code that loops through the databases to set the properties:

        ' Browse the database collection on the target server
        Dim lisDBs As DatabaseCollection
        lisDBs = srvMgmtServer.Databases
        Dim objDB As Database
        For Each objDB In lisDBs                    ' Loop through the databases on the server
            If Not objDB.IsSystemObject Then
                Dim objDBOptions As DatabaseOptions

                'objDB.CompatibilityLevel = CompatibilityLevel.Version90
                objDBOptions = objDB.DatabaseOptions
                objDBOptions.RecoveryModel = RecoveryModel.Simple
                objDBOptions.Alter()
                objDB.Alter()
            End If
        Next

I've commented the code setting the CompatibilityLevel because I sometimes run this on my SQL 2000 servers, but left it in the code so it's there when I need it. Setting the RecoveryModel to Simple solves my transaction log problem on my Dev/QA servers.

The important thing for me is that I don't have to open up the properties window for each database in Management Studio to make sure all the databases are in Simple Recovery mode - I just run this app and I know they're set correctly.

Allen