While tying your database objects into builds and your enterprise source control system takes a third-party product (like Visual Studio Database Edition or Red-Gate's SQL Source Control), you can achieve some protection using a single stored procedure that periodically and automatically takes a snapshot of all your objects and archives them. At any point in time, you can see the history for a given database object, and it makes it trivial to restore a previous version of an altered or dropped stored procedure.
This is accomplished using a stored procedure (provided in full at the end of the article) that's created in the master database, and then scheduled to run at whatever interval you choose - anywhere from once/minute to once/week, or anywhere in between.
This Stored Procedure goes through the following steps:
- 1
- If the Master.dbo.coSourceControl
table (used to store the history) doesn't exist, it creates it
- 2
- For each database on the server (so new databases are added automatically), it performs steps 3-5:
- 3
- It grabs the text contents of all the user objects (not flagged as "IsMsShipped")
- 4
- Compares the contents of each to the last known copy (if there is one)
- 5
- If the object is new or has changed, add a new copy to the source control table in master
- 6
- At the end, it will output the number of objects updated across all databases
The history is kept in a single table - master.dbo.coSourceControl
DETAILS:
The script involves some initial setup (creating the source control table if it doesn't exist, as well we setting up the SQL string that is executed), but there are three major segments that do the heavy lifting.
The initial cursor setup code selects which objects we're interested in - if there's any additional filter you want done (like excluding objects that start with "sp_", or if you only want stored procedures instead of all objects), you should do it using a WHERE clause here. In the provided segment, the only filters applied are just those that help filter our system objects - for this, "OBJECTPROPERTY(sc.id, 'IsMSShipped') = 0" is doing the heavy lifting, since it's the most reliably way to tell if an object is a system-included object, and it's supported as far back as SQL 2000.
The next segment that does major work is the actual INSERT into the source control database table - it's a multi-part query that links back to source control table a few times:
The OUTPUT clause is used to support the summary at the end of the whole process, and doesn't have any impact on the actual source control process itself. It only outputs a row when something is updated, so you can keep track of how many updates have been made throughout the process
It's easier to understand the query starting with the second virtual table, which is nested - it's being used to select the most recent version of the object in question (if a previous version of it exists). By selecting the MAX(SourceDate) for the object in the inner query, you can fetch the related text for that version in the outer query.
Once you have the most recent version (or a table with all NULL values if a previous version doesn't exist), then it compares it to the version you're currently working with - if the current version hasn't changed, then then both the name ObjectName and ObjectText will match, and nothing is inserted. If either of those fields is different, then it sees that it needs to save a new version of the object, and inserts it properly.
The last segment "puts it all together":
It uses the undocumented stored procedure "sp_MSforeachdb" (read about it here) - this stored procedure will run a given command in each database on the instance, and it's what allows this process to automatically support new databases on the server. When you add a new database to the server, it automatically becomes part of the list processed by sp_MSforeachdb.
A temp table is created to hold the results of every insert - each time a new version of an object is found and added the coSourceControl table, a record is added to this temp table (using the OUTPUT clause from the nested INSERT above). At the end, a SUM of this rowcount is taken to give the total number of objects for which new versions were found and recorded.
IMPLEMENTING IT:
To implement it, just grab the script from the bottom of the article and run it in the master database - it will create the stored procedure called coSourceControlRefresh. That's it - now either run it on demand whenever you want to back up a copy of your schema history, or you can schedule it to run on any desired interval using the SQL Agent. It will create the supporting table (if it's missing) and scan every database every time it's run. To see the history for an object:
Restoring a dropped or changed database object should be as simple as running the query above, grabbing the contents of ObjectText you're interested in, and then pasting it in another window and executing it. That's all it takes to restore the previous version of an object (and this stored proc should also, the next time it runs, see that you've altered the object and record that there's a "new" version of it).
Here's the complete contents of the script - grab this and run it in your master database to create the supporting stored procedure, and then you're ready to go!