August 2009
Mon Tue Wed Thu Fri Sat Sun
« Jul   Sep »
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Day August 14, 2009

Scripting Objects with Magic

You know what, there’s no magic here. However, it’s kinda magic to me since I didn’t write it and reading other people’s code is something that is almost like magic to me. And, I have to admit that I did not write this code. One of my co-workers, Brent Morrow, came up with this solution. Sadly, Brent does not have a blog. Luckily, I do. So now I will share with you Brent’s solution for keeping track of objects to move from one platform to another.

What’s the goal here? Well, let’s say we don’t have VSTS: Database Developer edition. And let’s further posit that we don’t have access to any of the commercially available schema comparison tools. Apart from pen and paper, how shall we keep track of the changes that need to go into production?

Well, we can use SQL Server to keep track of these changes.

The first thing we need is a table to track this:

CREATE TABLE [Debug].[DBObjects]
    (
      [LocalID] [int] IDENTITY(1, 1)
                      NOT NULL,
      [Schema] [nvarchar](128) NOT NULL,
      [Name] [nvarchar](128) NOT NULL,
      [ObjectType] [varchar](30) NOT NULL,
      [RequestedBy] [varchar](30) NULL,
      [RequestedDate] [datetime] NULL,
      [ProductionReady] [bit] NULL,
      [ProductionDate] [smalldatetime] NULL,
      [InProduction] [bit] NULL,
      [Comments] [varchar](2048) NULL,
      CONSTRAINT [PK_DBObjects] PRIMARY KEY CLUSTERED ( [LocalID] ASC )
        WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
               IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
               ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
    )
ON  [PRIMARY] ;

So, here are the rules for the table:

  • Schema – this is the object schema. It’s required.
  • Name – the name of the object to be scripted. Also required.
  • ObjectType – the type of object that we’re scripting. This needs to be one of the following values: STORED PROCEDURE, VIEW, FUNCTION. This is required. Spelling is important.
  • RequestedBy – this shows up in the script, but it’s not required.
  • RequestedDate – this also shows up in the script, but it’s not required.
  • ProductionReady – this is required, it needs to be set to 1.
  • ProductionDate – this is optional, it is just the date that something goes/went into production.
  • InProduction – One the object has been moved into production, set this to true (1). If you don’t do this, and ProductionReady is set to true (1), then the object will be scripted the next time that everything is run.
  • Comments – optional comments, these show up in the script.

So, what is the next step? Well, you need a way to script all of this. Basically, the following script will execute against the DBOjects table and will generate a script for all of the objects in the table with ProductionReady set to true and InProduct set to false. Once the script has been run against the new environment, set InProduction to true (or remove the row) and the object won’t be scripted in subsequent runs. If you have more changes, add a new line with a new comment and new request date. Good times, eh?

So, a big thanks to Brent Morrow for sharing this code with the world. If you have questions, comments, concerns, improvements, or just want to share kudos, put them down in the comments or drop me an email and I’ll make sure that Brent knows about it.

---------------------------------------------------------------
-- DEFINITIONS
---------------------------------------------------------------

CREATE TABLE #EntryHold ( [Text] VARCHAR(4000) )

CREATE TABLE #Entry ( [Text] VARCHAR(4000) )

DECLARE @LocalID INT
DECLARE @Schema VARCHAR(50)
DECLARE @Name VARCHAR(100)
DECLARE @ObjectType VARCHAR(100)
DECLARE @ProductionReady BIT
DECLARE @InProduction BIT
DECLARE @RequestedBy VARCHAR(50)
DECLARE @RequestedDate DATETIME
DECLARE @Comments VARCHAR(2000)
DECLARE @Counter TINYINT
DECLARE @Supported BIT

DECLARE [EntryCursor] SCROLL CURSOR
    FOR SELECT  [LocalID],
                [Schema],
                [Name],
                [ObjectType],
                [ProductionReady],
                [InProduction],
                [RequestedBy],
                [RequestedDate],
                [Comments]
        FROM    [Debug].[DBObjects]
        ORDER BY [LocalID]

---------------------------------------------------------------
-- HEADER
---------------------------------------------------------------
OPEN [EntryCursor]

-- STORED PROCEDURES NOT MOVED
INSERT  INTO #Entry
VALUES  (
          '--- OBJECTS NOT BEING MOVED ---'
        )

SET @Counter = 0
WHILE @Counter < 3
    BEGIN
        FETCH FIRST FROM [EntryCursor] INTO @LocalID, @Schema, @Name,
            @ObjectType, @ProductionReady, @InProduction, @RequestedBy,
            @RequestedDate, @Comments

        WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @Supported = CASE WHEN @ObjectType IN ( 'VIEW',
                                                            'STORED PROCEDURE',
                                                            'FUNCTION' )
                                      THEN 1
                                      ELSE 0
                                 END

                IF ( ( @ObjectType = 'VIEW'
                       AND @Counter = 0
                     )
                     OR ( @ObjectType = 'STORED PROCEDURE'
                          AND @Counter = 1
                        )
                     OR ( @ObjectType NOT IN ( 'STORED PROCEDURE', 'VIEW' )
                          AND @Counter = 2
                        )
                   )
                    AND ( @ProductionReady IS NULL
                          OR @ProductionReady = 0
                        )
                    AND ( @InProduction IS NULL
                          OR @InProduction = 0
                        )
                    BEGIN
                        INSERT  INTO #Entry
                        VALUES  (
                                  '--REQUESTOR: ' + @RequestedBy + SPACE(12 - LEN(@RequestedBy))
                                  + 'DATE: ' + CONVERT(VARCHAR, @RequestedDate, 101)
                                  + ' | ' + @ObjectType + ': ' + @Schema + '.'
                                  + @Name
                                )
                        IF @Supported = 1
                            INSERT  INTO #Entry
                            VALUES  (
                                      '--        REASON NOT MOVED:  Not production ready!'
                                    )
                        ELSE
                            INSERT  INTO #Entry
                            VALUES  (
                                      '--        REASON NOT MOVED:  Not Supported!'
                                    )

                        IF ( @Comments IS NOT NULL )
                            INSERT  INTO #Entry
                            VALUES  (
                                      '--        COMMENTS:  ' + @Comments
                                    )
                    END
                FETCH [EntryCursor] INTO @LocalID, @Schema, @Name, @ObjectType,
                    @ProductionReady, @InProduction, @RequestedBy,
                    @RequestedDate, @Comments
            END
        SET @Counter = @Counter + 1
    END
INSERT  INTO #Entry
VALUES  (
          '---------------------------------------------------------------------------------------------------'
        )

INSERT  INTO #Entry
VALUES  (
          '--- OBJECTS READY TO MOVE ---'
        )

SET @Counter = 0
WHILE @Counter < 3
    BEGIN
        FETCH FIRST FROM [EntryCursor] INTO @LocalID, @Schema, @Name,
            @ObjectType, @ProductionReady, @InProduction, @RequestedBy,
            @RequestedDate, @Comments
        WHILE @@FETCH_STATUS = 0
            BEGIN
                IF ( ( @ObjectType = 'VIEW'
                       AND @Counter = 0
                     )
                     OR ( @ObjectType = 'STORED PROCEDURE'
                          AND @Counter = 1
                        )
                     OR ( @ObjectType NOT IN ( 'STORED PROCEDURE', 'VIEW' )
                          AND @Counter = 2
                        )
                   )
                    AND ( @ProductionReady = 1 )
                    AND ( @InProduction IS NULL
                          OR @InProduction = 0
                        )
                    BEGIN
                        INSERT  INTO #Entry
                        VALUES  (
                                  '--REQUESTOR: ' + @RequestedBy + SPACE(12 - LEN(@RequestedBy))
                                  + 'DATE: ' + CONVERT(VARCHAR, @RequestedDate, 101)
                                  + ' | ' + @ObjectType + ': ' + @Schema + '.'
                                  + @Name
                                )
                        IF ( @Comments IS NOT NULL )
                            INSERT  INTO #Entry
                            VALUES  (
                                      '--        COMMENTS:  ' + @Comments
                                    )
                    END
                FETCH [EntryCursor] INTO @LocalID, @Schema, @Name, @ObjectType,
                    @ProductionReady, @InProduction, @RequestedBy,
                    @RequestedDate, @Comments
            END
        SET @Counter = @Counter + 1
    END

INSERT  INTO #Entry
VALUES  (
          '---------------------------------------------------------------------------------------------------'
        )
INSERT  INTO #Entry
VALUES  (
          '--- OBJECT SCRIPTS ---'
        )

---------------------------------------------------------------
-- SCRIPTS THAT ARE PRODUCTION READY
---------------------------------------------------------------
SET @Counter = 0
WHILE @Counter  5
                            BEGIN
                                INSERT  INTO #Entry
                                        SELECT  *
                                        FROM    #EntryHold
                            END
                        ELSE
                            BEGIN
                                INSERT  INTO #Entry
                                VALUES  (
                                          '--' + @ObjectType + ' [' + @Schema
                                          + '].[' + @Name + '] was not found.'
                                        )
                                INSERT  INTO #Entry
                                VALUES  (
                                          '---------------------------------------------------------------------------------------------------'
                                        )
                            END
                        DELETE  FROM #EntryHold
                    END

                FETCH [EntryCursor] INTO @LocalID, @Schema, @Name, @ObjectType,
                    @ProductionReady, @InProduction, @RequestedBy,
                    @RequestedDate, @Comments
            END
        SET @Counter = @Counter + 1
    END

---------------------------------------------------------------
-- SCRIPTS THAT ARE NOT PRODUCTION READY
---------------------------------------------------------------
CREATE TABLE #CommentEntry ( [Text] VARCHAR(4000) )

SET @Counter = 0
WHILE @Counter < 3
    BEGIN
        FETCH FIRST FROM [EntryCursor] INTO @LocalID, @Schema, @Name,
            @ObjectType, @ProductionReady, @InProduction, @RequestedBy,
            @RequestedDate, @Comments

        WHILE @@FETCH_STATUS = 0
            BEGIN
                IF ( ( @ObjectType = 'VIEW'
                       AND @Counter = 0
                     )
                     OR ( @ObjectType = 'STORED PROCEDURE'
                          AND @Counter = 1
                        )
                     OR ( @ObjectType NOT IN ( 'STORED PROCEDURE', 'VIEW' )
                          AND @Counter = 2
                        )
                   )
                    AND ( @ProductionReady IS NULL
                          OR @ProductionReady = 0
                        )
                    AND ( @InProduction IS NULL
                          OR @InProduction = 0
                        )
                    BEGIN
                        INSERT  INTO #CommentEntry
                                EXECUTE
                                       ( 'sp_helptext @objname = ''' + @Schema
                                         + '.' + @Name + ''''
                                       )
                        INSERT  INTO #CommentEntry
                        VALUES  ( 'GO' )
                        INSERT  INTO #CommentEntry
                        VALUES  (
                                  '---------------------------------------------------------------------------------------------------'
                                )
                    END

                FETCH [EntryCursor] INTO @LocalID, @Schema, @Name, @ObjectType,
                    @ProductionReady, @InProduction, @RequestedBy,
                    @RequestedDate, @Comments
            END
        SET @Counter = @Counter + 1
    END

INSERT  INTO #Entry
        SELECT  '--' + [Text]
        FROM    #CommentEntry

DROP TABLE #CommentEntry

CLOSE [EntryCursor]
DEALLOCATE [EntryCursor]

DROP TABLE #EntryHold

SELECT  *
FROM    #Entry
DROP TABLE #ENTRY

AppDev Virtual Chapter and CBusPASS Round Up

A special thanks to Grant Fritchey and Kevin Kline for their recent presentations.

Understanding Execution Plans

Grant Fritchey's Fists of Fury - putting execution plans in their place

Grant Fritchey's Fists of Fury - putting execution plans in their place

Grant Fritchey gave a phenomenal presentation covering more than just the plain basics about execution plans. Rather than focus on the wealth of potential operators that SQL Server can show in an execution plan, Grant focused on reading actual execution plans and showing the most common operations and what they mean. In addition to covering some ideal scenarios, Grant also discussed potential performance bottlenecks, how to investigate these potential bottlenecks, and what to do when they become an actual problem.

Unfortunately, due to technical mishaps (Grant and I both forgetting to start the presentation recording) there is no recording.

FEAR NOT! There will be a repeat of this presentation in October when Grant will be presenting virtually for the CBusPASS group.

End to End Performance Tuning

Kevin Kline happily donated his evening, skipped part of the DevLink keynote and drove me back to my hotel in order to present remotely for CBusPASS. Kevin outlined a performance tuning methodology that works universally across SQL Server 2005 and 2008. Kevin’s presentation, despite some technical hiccups, covered some great ground and reminded me that it’s alwasy best to start at the most basic level (error logs in both Windows and SQL Server) and move through the general layers of the OS, database, and then application while slowly narrowing down towards the specific problem and a working solution.

Bonus Fact: Kevin is red-green color blind.

This site is protected with Urban Giraffe's plugin 'HTML Purified' and Edward Z. Yang's Powered by HTML Purifier. 401 items have been purified.