We\'re upgrading from SQL Server 2005 to 2008. Almost every database in the 2005 instance is set to 2000 compatibility mode, but we\'re jumping to 2008. Our testing is compl
When I came across this question I was interested in finding a safe, non-invasive, and fast technique for validating syntax and object (table, column) references.
While I agree that actually executing each stored procedure will likely turn up more issues than just compiling them, one must exercise caution with the former approach. That is, you need to know that it is, in fact, safe to execute each and every stored procedure (i.e. does it erase some tables, for example?). This safety issue can be addressed by wrapping the execution in a transaction and rolling it back so no changes are permanent, as suggested in devio's answer. Still, this approach could potentially take quite a long time depending on how much data you are manipulating.
The code in the question, and the first portion of Oleg's answer, both suggest re-instantiating each stored procedure, as that action recompiles the procedure and does just such syntactic validation. But this approach is invasive--it's fine for a private test system, but could disrupt the work of other develoeprs on a heavily used test system.
I came across the article Check Validity of SQL Server Stored Procedures, Views and Functions, which presents a .NET solution, but it is the follow-up post at the bottom by "ddblue" that intrigued me more. This approach obtains the text of each stored procedure, converts the create keyword to alter so that it can be compiled, then compiles the proc. And that accurately reports any bad table and column references. The code runs, but I quickly ran into some issues because of the create/alter conversion step.
The conversion from "create" to "alter" looks for "CREATE" and "PROC" separated by a single space. In the real-world, there could spaces or tabs, and there could be one or more than one. I added a nested "replace" sequence (thanks, to this article by Jeff Moden!) to convert all such occurrences to a single space, allowing the conversion to proceed as originally designed. Then, since that needed to be used wherever the original "sm.definition" expression was used, I added a common table expression to avoid massive, unsightly code duplication. So here is my updated version of the code:
DECLARE @Schema NVARCHAR(100),
@Name NVARCHAR(100),
@Type NVARCHAR(100),
@Definition NVARCHAR(MAX),
@CheckSQL NVARCHAR(MAX)
DECLARE crRoutines CURSOR FOR
WITH System_CTE ( schema_name, object_name, type_desc, type, definition, orig_definition)
AS -- Define the CTE query.
( SELECT OBJECT_SCHEMA_NAME(sm.object_id) ,
OBJECT_NAME(sm.object_id) ,
o.type_desc ,
o.type,
REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(REPLACE(sm.definition, char(9), ' '))), ' ', ' ' + CHAR(7)), CHAR(7) + ' ', ''), CHAR(7), '') [definition],
sm.definition [orig_definition]
FROM sys.sql_modules (NOLOCK) AS sm
JOIN sys.objects (NOLOCK) AS o ON sm.object_id = o.object_id
-- add a WHERE clause here as indicated if you want to test on a subset before running the whole list.
--WHERE OBJECT_NAME(sm.object_id) LIKE 'xyz%'
)
-- Define the outer query referencing the CTE name.
SELECT schema_name ,
object_name ,
type_desc ,
CASE WHEN type_desc = 'SQL_STORED_PROCEDURE'
THEN STUFF(definition, CHARINDEX('CREATE PROC', definition), 11, 'ALTER PROC')
WHEN type_desc LIKE '%FUNCTION%'
THEN STUFF(definition, CHARINDEX('CREATE FUNC', definition), 11, 'ALTER FUNC')
WHEN type = 'VIEW'
THEN STUFF(definition, CHARINDEX('CREATE VIEW', definition), 11, 'ALTER VIEW')
WHEN type = 'SQL_TRIGGER'
THEN STUFF(definition, CHARINDEX('CREATE TRIG', definition), 11, 'ALTER TRIG')
END
FROM System_CTE
ORDER BY 1 , 2;
OPEN crRoutines
FETCH NEXT FROM crRoutines INTO @Schema, @Name, @Type, @Definition
WHILE @@FETCH_STATUS = 0
BEGIN
IF LEN(@Definition) > 0
BEGIN
-- Uncomment to see every object checked.
-- RAISERROR ('Checking %s...', 0, 1, @Name) WITH NOWAIT
BEGIN TRY
SET PARSEONLY ON ;
EXEC ( @Definition ) ;
SET PARSEONLY OFF ;
END TRY
BEGIN CATCH
PRINT @Type + ': ' + @Schema + '.' + @Name
PRINT ERROR_MESSAGE()
END CATCH
END
ELSE
BEGIN
RAISERROR ('Skipping %s...', 0, 1, @Name) WITH NOWAIT
END
FETCH NEXT FROM crRoutines INTO @Schema, @Name, @Type, @Definition
END
CLOSE crRoutines
DEALLOCATE crRoutines