43

How to check all stored procedure is ok in sql server if I drop a table or fields?

Cade Roux
  • 88,164
  • 40
  • 182
  • 265
leo
  • 451
  • 1
  • 4
  • 4

10 Answers10

58

I found Cade's answer useful in formulating my own script for checking objects in a database, so I thought I'd share my script as well:

DECLARE @Name nvarchar(1000);
DECLARE @Sql nvarchar(1000);
DECLARE @Result int;

DECLARE ObjectCursor CURSOR FAST_FORWARD FOR
SELECT QUOTENAME(SCHEMA_NAME(o.schema_id)) + '.' + QUOTENAME(OBJECT_NAME(o.object_id))
FROM sys.objects o
WHERE type_desc IN (
'SQL_STORED_PROCEDURE',
'SQL_TRIGGER',
'SQL_SCALAR_FUNCTION',
'SQL_TABLE_VALUED_FUNCTION',
'SQL_INLINE_TABLE_VALUED_FUNCTION',
'VIEW')
    --include the following if you have schema bound objects since they are not supported
    AND ISNULL(OBJECTPROPERTY(o.object_id, 'IsSchemaBound'), 0) = 0
;

OPEN ObjectCursor;

FETCH NEXT FROM ObjectCursor INTO @Name;

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @Sql = N'EXEC sp_refreshsqlmodule ''' + @Name + '''';
    --PRINT @Sql;

    BEGIN TRY
        EXEC @Result = sp_executesql @Sql;
        IF @Result <> 0 RAISERROR('Failed', 16, 1);
    END TRY
    BEGIN CATCH
        PRINT 'The module ''' + @Name + ''' does not compile.';
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH

    FETCH NEXT FROM ObjectCursor INTO @Name;
END

CLOSE ObjectCursor;
DEALLOCATE ObjectCursor;
Michael Petito
  • 12,891
  • 4
  • 40
  • 54
  • 2
    The only way to refresh triggers is to alter them if not encrypted. (http://connect.microsoft.com/SQLServer/feedback/details/261905/sp-refreshsqlmodule-does-not-work-with-triggers). So the above script gives false positives when it encounters a trigger – buckley Feb 16 '12 at 09:22
  • This worked really well for me. (although I don't have any triggers) – Jason Parker May 29 '14 at 21:10
  • 4
    Nice post, works perfect! One more addition, you have to check if object is schema bound as sp_refreshsqlmodule will give false negatives as schema bound objects are not supported. So add to your query the following: and isnull(objectproperty(o.object_id,'IsSchemaBound'),0)=0 ; – George Mavritsakis Sep 05 '14 at 19:41
  • 3
    I'd also change line 6 to `SELECT SCHEMA_NAME(o.schema_id) + '.[' + OBJECT_NAME(o.object_id) + ']'` in case you have stored procedures with a dot in the name. – Drew Freyling Dec 03 '14 at 04:12
  • 1
    @DrewFreyling Good idea! You can also use `QUOTENAME` to quote object names as such. I'll update the answer. – Michael Petito Dec 03 '14 at 04:24
  • 3
    I tried this script on a stored procedure that references a non-existent table, and it didn't highlight it. Sure enough, if I just run sp_refreshsqlmodule 'mysp' it doesn't report this stored proc either. – Amit G Oct 18 '17 at 16:58
  • Useful script. Two other scenarios where it can fail: 1 - you have a procedure/function name that contains a single quote (sql injection via db object names - woohoo!), 2 - you have a scalar UDF that is used in a CHECK constraint on table column. I *hope* neither of these scenarios are common... – Nathan Jul 11 '18 at 21:14
  • A warning on sp_refreshsqlmodule, if an object is renamed in SSMS or renamed using sp_rename the object name in sys.sql_modules is not changed. Running the sp_refreshview or refreshsqlmodule will rebuild the object using whatever sql_modules has for that name, which could be a different definition than what the object currently is! – tvanharp Oct 08 '18 at 14:39
  • DANGER: This will re-activate any triggers that are disabled. I had a few triggers that were disabled due to a system migration that was in progress. They were re-enabled and it caused pretty much everything to crash. – Chris Rice Jan 25 '19 at 20:48
  • 1
    I know this is an old post, but I think very useful, only one question: why we need ROLLBACK TRANSACTION in the catch block? – Dejan Dozet Apr 14 '19 at 10:32
  • " (2) - you have a scalar UDF that is used in a CHECK constraint on table column". Exclude these functions by using `sys.sql_expression_dependencies` : Add to the where clause `AND o.object_id NOT IN (select oo.object_id from sys.check_constraints s INNER JOIN sys.sql_expression_dependencies d ON d.referencing_id = s.object_id INNER JOIN sys.objects oo on oo.object_id = d.referenced_id AND oo.type = 'FN')` @nathan – yodag123 Feb 12 '21 at 05:04
9

It won't catch everything (dynamic SQL or latebound objects), but it can be useful - call sp_refreshsqlmodule on all non-schema bound stored procedures (you can call it before to ensure that dependencies are updated and then query the dependencies, or call it afterwards and see if anything is broken):

DECLARE @template AS varchar(max)
SET @template = 'PRINT ''{OBJECT_NAME}''
EXEC sp_refreshsqlmodule ''{OBJECT_NAME}''

'

DECLARE @sql AS varchar(max)

SELECT  @sql = ISNULL(@sql, '') + REPLACE(@template, '{OBJECT_NAME}',
                                          QUOTENAME(ROUTINE_SCHEMA) + '.'
                                          + QUOTENAME(ROUTINE_NAME))
FROM    INFORMATION_SCHEMA.ROUTINES
WHERE   OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.'
                                 + QUOTENAME(ROUTINE_NAME)),
                       N'IsSchemaBound') IS NULL
        OR OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.'
                                    + QUOTENAME(ROUTINE_NAME)),
                          N'IsSchemaBound') = 0

        EXEC (
              @sql
            )
Cade Roux
  • 88,164
  • 40
  • 182
  • 265
  • 1
    It seems sp_refreshsqlmodule [can end up refreshing the wrong module however!](https://connect.microsoft.com/SQLServer/feedback/details/656863/sp-refreshsqlmodule-corrupts-renamed-objects-definitions) – Martin Smith Apr 01 '11 at 19:59
  • @Martin Yes, I should have mentioned that. In my case, because it was an ALTER and there wasn't another procedure using the same name, I got an error, it didn't hurt anything. – Cade Roux Apr 02 '11 at 15:45
  • Works perfectly, saved me lots of time – BunkerBilly Oct 23 '19 at 11:00
  • I've just found a proc that refers to a non existing database that saves OK and passes `sp_refreshsqlmodule` – Nick.Mc Nov 24 '21 at 07:07
7

I basically did the same thing, but wrote it to be CURSORless which is super fast.

DECLARE @Name nvarchar(1000);
DECLARE @Sql nvarchar(1000);
DECLARE @Result int;

DECLARE @Objects TABLE (
    Id INT IDENTITY(1,1),
    Name nvarchar(1000)
)

INSERT INTO @Objects
SELECT QUOTENAME(SCHEMA_NAME(o.schema_id)) + '.' + QUOTENAME(OBJECT_NAME(o.object_id))
FROM sys.objects o
WHERE type_desc IN (
'SQL_STORED_PROCEDURE',
'SQL_TRIGGER',
'SQL_SCALAR_FUNCTION',
'SQL_TABLE_VALUED_FUNCTION',
'SQL_INLINE_TABLE_VALUED_FUNCTION',
'VIEW')
    --include the following if you have schema bound objects since they are not supported
    AND ISNULL(OBJECTPROPERTY(o.object_id, 'IsSchemaBound'), 0) = 0

DECLARE @x INT
DECLARE @xMax INT

SELECT @xMax = MAX(Id) FROM @Objects
SET @x = 1

WHILE @x < @xMax
BEGIN
    SELECT @Name = Name FROM @Objects WHERE Id = @x

    SET @Sql = N'EXEC sp_refreshsqlmodule ''' + @Name + '''';
    --PRINT @Sql;

    BEGIN TRY
        EXEC @Result = sp_executesql @Sql;
        IF @Result <> 0 RAISERROR('Failed', 16, 1);
    END TRY
    BEGIN CATCH
        PRINT 'The module ''' + @Name + ''' does not compile.';
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH
    SET @x = @x + 1
END
Danny G
  • 3,660
  • 4
  • 38
  • 50
  • 3
    Using a while statement to loop through a table of data is basically the same as using a cursor with fast_forward. Both are going row by agonising row (RBAR). This is one situation where it is acceptable since it cannot be replaced by a set based operation. – Nicholas Jan 12 '16 at 03:50
  • This does perform a bit better than using the cursor, even if it still uses a while loop. – Dan Field Feb 18 '16 at 14:17
6

In addition to the script from Michael Petito you can check for issues with late-bound objects in SPs (deferred name resolution) like this:

-- Based on comment from http://blogs.msdn.com/b/askjay/archive/2012/07/22/finding-missing-dependencies.aspx
-- Check also http://technet.microsoft.com/en-us/library/bb677315(v=sql.110).aspx

select o.type, o.name, ed.referenced_entity_name, ed.is_caller_dependent
from sys.sql_expression_dependencies ed
join sys.objects o on ed.referencing_id = o.object_id
where ed.referenced_id is null
2

Couple of ways that come to mind

  1. Most obvious way run the procedures
  2. check dependencies on the table before you drop the table or a field. then check out those dependent proceudres
  3. generate scripts on all procedures and search for that field or table
  4. Query sysobjects
John Hartsock
  • 85,422
  • 23
  • 131
  • 146
2

Once I made change to a table such as column rename, I have to alter all the stored procedures, functions and views that refer the table column. Obviously I have to manually alter them one by one. But my database contains hundreds of objects like these. So I wanted to make sure I have altered all the depending objects. One solution is to recompile all the objects (via a script). But recompilation happens on each object’s next execution only. But what I want is to validate them and get the details now.

For that I can use “sp_refreshsqlmodule” instead of “sp_recompile”. This will refresh each object and throws an error if its not parsing correctly. Here is the script below;

 -- table variable to store procedure names
    DECLARE @tblObjects TABLE (ObjectID INT IDENTITY(1,1), ObjectName 
    sysname)

   -- get the list of stored procedures, functions and views
    INSERT INTO @tblObjects(ObjectName)
    SELECT '[' + sc.[name] + '].[' + obj.name + ']'
    FROM sys.objects obj
    INNER JOIN sys.schemas sc ON sc.schema_id = obj.schema_id
    WHERE obj.[type] IN ('P', 'FN', 'V') -- procedures, functions, views

    -- counter variables
    DECLARE @Count INT, @Total INT
    SELECT @Count = 1
    SELECT @Total = COUNT(*) FROM @tblObjects

    DECLARE @ObjectName sysname

    -- start the loop
    WHILE @Count <= @Total BEGIN

    SELECT @ObjectName = ObjectName
    FROM @tblObjects
    WHERE ObjectID = @Count

    PRINT 'Refreshing... ' + @ObjectName

    BEGIN TRY
        -- refresh the stored procedure
        EXEC sp_refreshsqlmodule @ObjectName
    END TRY
    BEGIN CATCH
        PRINT 'Validation failed for : ' + @ObjectName + ', Error:' + 
        ERROR_MESSAGE() + CHAR(13)
    END CATCH

    SET @Count = @Count + 1

    END

If any object throws an error I can now attend to it and manually fix the issue with it.

2

None of the answers given can find the error resulting from renaming or dropping a table
but be happy, I have a solution on SQL Server 2017 and higher versions:

DECLARE @NumberRecords INT
DECLARE @RowCount INT
DECLARE @Name NVARCHAR(MAX)
DECLARE @Command NVARCHAR(MAX)
DECLARE @Result int
DECLARE @Names TABLE (
    [RowId] INT NOT NULL    IDENTITY(1, 1),
    [Name]  NVARCHAR(MAX),
    [Type]  NVARCHAR(MAX)
)

INSERT INTO @Names
SELECT
        QUOTENAME(SCHEMA_NAME([Objects].schema_id)) + '.' + QUOTENAME(OBJECT_NAME([Objects].object_id)) [Name],
        type_desc [Type]
FROM sys.objects [Objects]
WHERE type_desc IN ('SQL_STORED_PROCEDURE',
                    'SQL_TRIGGER',
                    'SQL_SCALAR_FUNCTION',
                    'SQL_TABLE_VALUED_FUNCTION',
                    'SQL_INLINE_TABLE_VALUED_FUNCTION',
                    'VIEW')
ORDER BY [Name]

SET @RowCount = 1
SET @NumberRecords = (SELECT COUNT(*) FROM @Names)
WHILE (@RowCount <= @NumberRecords)
BEGIN

    SELECT @Name = [Name]
    FROM @Names
    WHERE [RowId] = @RowCount

    SET @Command = N'EXEC sp_refreshsqlmodule ''' + @Name + ''''

    BEGIN TRY

        EXEC @Result = sp_executesql @Command

        IF @Result <> 0
        BEGIN

            RAISERROR('Failed', 16, 1)

        END
        ELSE
        BEGIN

            IF (NOT EXISTS (SELECT *
                            FROM sys.dm_sql_referenced_entities(@Name, 'OBJECT')
                            WHERE [is_incomplete] = 1))
            BEGIN

                DELETE
                FROM @Names
                WHERE [RowId] = @RowCount

            END

        END

    END TRY
    BEGIN CATCH

        -- Nothing

    END CATCH

    SET @RowCount = @RowCount + 1

END

SELECT  [Name],
        [Type]
FROM @Names
  • `is_incomplete` doesn't seem to exist in my `sys.dm_sql_referenced_entities`, I tried your script with `is_all_columns_found` instead, but it returns too many false positives... – Louis Somers Aug 06 '19 at 15:09
  • This seems to work better: `IF (NOT EXISTS (SELECT 1 FROM sys.dm_sql_referenced_entities(@Name, 'OBJECT') WHERE is_all_columns_found = 0 AND referenced_minor_name IS NOT null))` – Louis Somers Aug 06 '19 at 15:22
  • I wrote and tested this script on SQL Server 2017 –  Aug 07 '19 at 03:55
1

I tried "Cade Roux" Answer , it went wrong and I fixed it as following

 SELECT 'BEGIN TRAN T1;' UNION
    SELECT   REPLACE('BEGIN TRY
    EXEC sp_refreshsqlmodule ''{OBJECT_NAME}''
      END TRY
      BEGIN CATCH
    PRINT ''{OBJECT_NAME} IS INVALID.'' 
     END CATCH', '{OBJECT_NAME}',
                                              QUOTENAME(ROUTINE_SCHEMA) + '.'
                                              + QUOTENAME(ROUTINE_NAME))
    FROM    INFORMATION_SCHEMA.ROUTINES
    WHERE   OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.'
                                     + QUOTENAME(ROUTINE_NAME)),
                           N'IsSchemaBound') IS NULL
            OR OBJECTPROPERTY(OBJECT_ID(QUOTENAME(ROUTINE_SCHEMA) + '.'
                                        + QUOTENAME(ROUTINE_NAME)),
                              N'IsSchemaBound') = 0
                              UNION 
                            SELECT  'ROLLBACK TRAN T1;'
1

Same idea, but more universal - you check all user defined objects with bodies And it shows you error during compiling. This is really useful after renaming/removing objects/columns etc

Just run it after database schema update to make sure that all body objects still valid

DECLARE @obj_name AS sysname, @obj_type AS sysname

DECLARE obj_cursor CURSOR FOR 
    SELECT SCHEMA_NAME(o.schema_id) + '.' + o.name, o.type_desc 
    FROM sys.objects o 
    INNER JOIN sys.sql_modules m ON o.object_id = m.object_id
    WHERE o.is_ms_shipped = 0 AND m.is_schema_bound = 0 
    ORDER BY o.type_desc, SCHEMA_NAME(o.schema_id), o.name

OPEN obj_cursor 
FETCH NEXT FROM obj_cursor INTO @obj_name, @obj_type

WHILE (@@FETCH_STATUS <> -1) 
BEGIN
    BEGIN TRY
        EXEC sp_refreshsqlmodule @obj_name
        --PRINT 'Refreshing ''' + @obj_name + ''' completed'
    END TRY
    BEGIN CATCH
        PRINT 'ERROR - ' + @obj_type + ' ''' + @obj_name + ''':' + ERROR_MESSAGE()
    END CATCH
    FETCH NEXT FROM obj_cursor INTO @obj_name, @obj_type
END 

CLOSE obj_cursor
DEALLOCATE obj_cursor
0

My approach was a little bit different. I've created alter script for a bunch of procs in SSMS and then waited for few seconds so SSMS process them and Ive got what I wanted:

enter image description here

O then SSMS right border a red dot for any line in error, which I can easily check, correct and later execute same script to update with correct values.

Dejan Dozet
  • 948
  • 10
  • 26