Tuesday, April 12, 2011

SP Recompiles ALL User Objects in the database

I worked on a project once where I needed to develop against a local copy of the database. This is not uncommon, especially if I am the only developer on the project. However, this project had three developers all working against our own local copy of the database. We were all off-site and separate from each other, and had to sync up our databases when we met each week. We used a database comparison tool that pointed out to us which of the stored procedures were different. One of us had updated the stored procedure while performing our work, while the other developer may not have touched his copy of the same stored procedure.

So we performed the comparison between my copy of the database and his copy of the database, decided on which stored procedure we wanted to keep, and told the database comparison tool to sync the master database with the selected stored procedure. Here is where we had the problem.

The problem was that the stored procedure we selected wouldn't work because the underlying database structure the stored procedure used had been changed, so the sync failed. We had to first figure out how to re-write the stored procedure to used the updated database structure, made sure it could be saved (compiled), and then go back and try to re-sync the databases objects again.

The underlying problem is that stored procedures can be created and saved (compiled) correctly. But then, days later, you can change the underlying table structure, and SQL gives you no warning (when you make the table structure change) which, or even that, stored procedures that depend on that table structure will no longer work (compile).

We had to come up with a way to easily re-compile all of our stored procedures (over 200) without manually opening up each one and clicking save.

Enter this stored procedure. After searching around on the internet on varios SQL related sites for what seemed like hours, I wasn't able to find anything like this. Maybe there's one out there somewhere, I just didn't have the time to keep looking.

So, I created this stored procedure to re-compile USER stored procedures, views, and functions in the database and tell me which ones didn't compile. Then, I could open only broken ones, fix them and save (compile) them. Then, I'd run this stored procedure again to check that I hadn't broken any others by fixing the reported broken ones. I repeated this procedure until all of the stored procedures, views, and functions in MY copy of the database compiled correctly.

I then had the other developers run the same procedure against their copy of the database. It was amazing to discover how many broken (uncompileable) objects we had between us. No wonder we were having so many problems syncing our databases. Once we ran this procedure on each of our databases, our synchronization only had to sync the database table changes, then select the stored procedure to sync that used the same database structure.

All in all, this procedure we begain enforcing among ourselves prior to any attempt to sync our disconnected databases. It worked like a charm. Hope you can find a use for it too.

/*
 =============================================
 Author:       Gary Janecek
 Create date:  5/4/2010
 Description:  Recompiles ALL User Objects in the database.
               
               The first part of the code inserted the names 
               of all stored procedures to a table variable,
               along with their schema names.  
               
               The next part of the code inserted the names 
               of all views to a table variable,
               along with their schema names.
               
               The next part of the code inserted the names 
               of all user defined functions to a table variable,
               along with their schema names.

               A table
               variable is used just to avoid a CURSOR.  The
               WHILE loop then reads each object name
               and passes it to the system stored procedure:
               sp_refreshsqlmodule.
               
               sp_refreshsqlmodule re-compiles the object
               and will throw an error if the validation fails.
               The CATCH block catches the error if the validation
               fails, and displays the error message in the 
               output window.
               
        NOTE:  Due to an unknown condition (so far) this script
               will fail on all objects AFTER A FAIL
               OCCURS.  Fix the first object that failed
               and re-run this again to find the next problem.
               Repeat until all objects are fixed.
               
 Sample Call: EXEC aDBMaint_ReCompileAllDatabaseObjects
 
 =============================================
*/
CREATE PROCEDURE [dbo].[aDBMaint_ReCompileAllDatabaseObjects]
AS
BEGIN
 -- SET NOCOUNT ON added to prevent extra result sets from
 -- interfering with SELECT statements.
 SET NOCOUNT ON;

    -- Insert statements for procedure here
    -- table variable to database object names.
    -- NOTE the IDENTITY FIELD so we don't need a CURSOR
 DECLARE @myListOfObjects TABLE (RowID INT IDENTITY(1,1)
                               , ObjectName sysname
                               , ObjectType varchar(max))
 
 -- retrieve the list of stored porcedures
 INSERT INTO @myListOfObjects(ObjectName, ObjectType)
  SELECT
   '[' + s.[name] + '].[' + sp.name + ']' AS ObjectName, 'StoredProcedure' AS ObjectType
   FROM sys.procedures sp
   INNER JOIN sys.schemas s ON s.schema_id = sp.schema_id
   WHERE is_ms_shipped = 0
   ORDER BY ObjectName
   
 -- retrieve the list of views
 INSERT INTO @myListOfObjects(ObjectName, ObjectType)
  SELECT
   '[' + s.[name] + '].[' + vw.name + ']' AS ObjectName, 'View' AS ObjectType
   FROM sys.views vw
   INNER JOIN sys.schemas s ON s.schema_id = vw.schema_id
   WHERE is_ms_shipped = 0
   ORDER BY ObjectName
   
 -- retrieve the list of functions
 INSERT INTO @myListOfObjects(ObjectName, ObjectType)
  SELECT
   '[' + s.[name] + '].[' + func.name + ']' AS FunctionName, 'Function' AS ObjectType
   FROM sys.objects func
   INNER JOIN sys.schemas s ON s.schema_id = func.schema_id
   WHERE is_ms_shipped = 0
   AND type_desc LIKE '%FUNCTION%' 
   ORDER BY FunctionName
      
 -- counter variables
 DECLARE @RowNumber INT
 DECLARE @TotalRows INT
 SELECT @RowNumber = 1
 SELECT @TotalRows = COUNT(*) FROM @myListOfObjects
 
 DECLARE @ThisObjectName sysname
 
 -- Start the loop
 WHILE @RowNumber < @TotalRows BEGIN
  SELECT @ThisObjectName = ObjectName FROM @myListOfObjects WHERE RowID = @RowNumber
  SELECT @ThisObjectType = ObjectType FROM @myListOfObjects WHERE RowID = @RowNumber
  
  PRINT N'Refreshing... ' + @ThisObjectType + SPACE(16-LEN(@ThisObjectType)) + ': ' + @ThisObjectName 
  
  BEGIN TRY
   -- Refresh the Object
   EXEC sp_refreshsqlmodule @ThisObjectName
  END TRY
  
  BEGIN CATCH
   PRINT 'Validation failed for : ' + @ThisObjectName + ', Error:' + ERROR_MESSAGE()
  END CATCH

  SET @RowNumber = @RowNumber + 1
 END  
END

No comments:

Post a Comment