Когда я наткнулся на этот вопрос, меня заинтересовал поиск безопасного, неинвазивного и быстрого метода проверки синтаксиса и ссылок на объекты (таблицы, столбцы).
Хотя я согласен, что фактическое выполнение каждой хранимой процедуры, скорее всего, вызовет больше проблем, чем просто их компиляция, следует соблюдать осторожность при первом подходе. То есть вам нужно знать, что на самом деле безопасно выполнять каждую хранимую процедуру (например, стирает ли она некоторые таблицы, например?). Эту проблему безопасности можно решить, обернув выполнение в транзакции и откатив ее назад, чтобы никакие изменения не были постоянными, как предложено в ответе devio. Тем не менее, этот подход может занять довольно много времени в зависимости от объема данных, которыми вы манипулируете.
Код в вопросе и первая часть ответа Олега предлагают заново создать экземпляр каждой хранимой процедуры, поскольку это действие перекомпилирует процедуру и выполняет только такую синтаксическую проверку. Но этот подход инвазивен - он подходит для частной тестовой системы, но может нарушить работу других разработчиков в интенсивно используемой тестовой системе.
Я наткнулся на статью Проверка допустимости хранимых процедур, представлений и функций SQL Server , в которой представлено решение .NET, но это последующее сообщение внизу "ddblue", который заинтриговал меня больше. Этот подход позволяет получить текст каждой хранимой процедуры, преобразовать ключевое слово create
в alter
, чтобы его можно было скомпилировать, а затем скомпилировать процедуру. И это точно сообщает о любых неверных ссылках на таблицы и столбцы. Код выполняется, но я быстро столкнулся с некоторыми проблемами из-за шага преобразования создания / изменения.
Преобразование из «create» в «alter» ищет слова «CREATE» и «PROC», разделенные одним пробелом. В реальном мире могут быть пробелы или табуляции, и их может быть один или несколько. Я добавил вложенную последовательность «заменить» (спасибо этой статье Джеффа Модена!), Чтобы преобразовать все такие вхождения в один пробел, позволяя преобразованию проходить так, как было изначально задумано. Затем, поскольку это нужно было использовать везде, где использовалось оригинальное выражение «sm.definition», я добавил общее табличное выражение, чтобы избежать массивного, неприглядного дублирования кода. Итак, вот моя обновленная версия кода:
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