Надежный способ проверки хранимых процедур T-SQL - PullRequest
13 голосов
/ 07 мая 2010

Мы обновляем SQL Server 2005 до 2008. Почти для каждой базы данных в экземпляре 2005 установлен режим совместимости 2000, но мы переходим к 2008 году. Наше тестирование завершено, но мы узнали, что мы нужно быстрее на это.

Я обнаружил некоторые хранимые процедуры, которые либо ВЫБИРАЮТ данные из отсутствующих таблиц, либо пытаются упорядочить несуществующие столбцы.

Обтекание SQL для создания процедур в SET PARSEONLY ON и перехват ошибок в try / catch только перехватывает недопустимые столбцы в ORDER BY. Он не находит ошибку с процедурой выбора данных из отсутствующей таблицы. Интеллектуальный смысл SSMS 2008, однако, ДЕЙСТВИТЕЛЬНО находит проблему, но я все еще могу продолжить и успешно запустить сценарий ALTER для этой процедуры без каких-либо жалоб.

Итак, почему я могу даже избежать создания процедуры, которая завершается с ошибкой при ее запуске? Есть ли какие-нибудь инструменты, которые могут работать лучше, чем я пробовал?

Первый инструмент, который я нашел, был не очень полезен: DbValidator из CodeProject , но он обнаружил меньше проблем, чем этот скрипт, который я обнаружил в SqlServerCentral, который нашел недопустимые ссылки на столбцы.

-------------------------------------------------------------------------
-- Check Syntax of Database Objects
-- Copyrighted work.  Free to use as a tool to check your own code or in 
--  any software not sold. All other uses require written permission.
-------------------------------------------------------------------------
-- Turn on ParseOnly so that we don't actually execute anything.
SET PARSEONLY ON 
GO

-- Create a table to iterate through
declare @ObjectList table (ID_NUM int NOT NULL IDENTITY (1, 1), OBJ_NAME varchar(255), OBJ_TYPE char(2))

-- Get a list of most of the scriptable objects in the DB.
insert into @ObjectList (OBJ_NAME, OBJ_TYPE)
SELECT   name, type
FROM     sysobjects WHERE type in ('P', 'FN', 'IF', 'TF', 'TR', 'V')
order by type, name

-- Var to hold the SQL that we will be syntax checking
declare @SQLToCheckSyntaxFor varchar(max)
-- Var to hold the name of the object we are currently checking
declare @ObjectName varchar(255)
-- Var to hold the type of the object we are currently checking
declare @ObjectType char(2)
-- Var to indicate our current location in iterating through the list of objects
declare @IDNum int
-- Var to indicate the max number of objects we need to iterate through
declare @MaxIDNum int
-- Set the inital value and max value
select  @IDNum = Min(ID_NUM), @MaxIDNum = Max(ID_NUM)
from    @ObjectList

-- Begin iteration
while @IDNum <= @MaxIDNum
begin
  -- Load per iteration values here
  select  @ObjectName = OBJ_NAME, @ObjectType = OBJ_TYPE
  from    @ObjectList
  where   ID_NUM = @IDNum 

  -- Get the text of the db Object (ie create script for the sproc)
  SELECT @SQLToCheckSyntaxFor = OBJECT_DEFINITION(OBJECT_ID(@ObjectName, @ObjectType))

  begin try
    -- Run the create script (remember that PARSEONLY has been turned on)
    EXECUTE(@SQLToCheckSyntaxFor)
  end try
  begin catch
    -- See if the object name is the same in the script and the catalog (kind of a special error)
    if (ERROR_PROCEDURE() <> @ObjectName)
    begin
      print 'Error in ' + @ObjectName
      print '  The Name in the script is ' + ERROR_PROCEDURE()+ '. (They don''t match)'
    end
    -- If the error is just that this already exists then  we don't want to report that.
    else if (ERROR_MESSAGE() <> 'There is already an object named ''' + ERROR_PROCEDURE() + ''' in the database.')
    begin
      -- Report the error that we got.
      print 'Error in ' + ERROR_PROCEDURE()
      print '  ERROR TEXT: ' + ERROR_MESSAGE() 
    end
  end catch

  -- Setup to iterate to the next item in the table
  select  @IDNum = case
            when Min(ID_NUM) is NULL then @IDNum + 1
            else Min(ID_NUM)
          end  
  from    @ObjectList
  where   ID_NUM > @IDNum

end
-- Turn the ParseOnly back off.
SET PARSEONLY OFF 
GO

Ответы [ 6 ]

7 голосов
/ 07 мая 2010

Вы можете выбрать разные способы. Прежде всего, SQL SERVER 2008 поддерживает зависимости , которые существуют в БД включающих зависимостях STORED PROCEDURE (см. http://msdn.microsoft.com/en-us/library/bb677214%28v=SQL.100%29.aspx, http://msdn.microsoft.com/en-us/library/ms345449.aspx и http://msdn.microsoft.com/en-us/library/cc879246.aspx).. Вы можете использовать sys.sql_expression_dependencies и sys.dm_sql_referenced_entities, чтобы увидеть и проверить там.

Но самый простой способ проверки всех хранимых процедур заключается в следующем:

  1. экспорт всех хранимых процедур
  2. отбросить старую существующую ПРОЦЕДУРУ
  3. импорт только что экспортированной ХРАНЕННОЙ ПРОЦЕДУРЫ.

Если вы обновите БД, существующая хранимая процедура не будет проверена, но если вы создадите новую, процедура будет проверена. Таким образом, после экспорта и экспорта всех хранимых процедур вы получите все существующие сообщения об ошибках.

Вы также можете просмотреть и экспортировать код хранимой процедуры с кодом, подобным следующему

SELECT definition
FROM sys.sql_modules
WHERE object_id = (OBJECT_ID(N'spMyStoredProcedure'))

ОБНОВЛЕНО : Чтобы увидеть объекты (например, таблицы и представления), на которые ссылается хранимая процедура spMyStoredProcedure, вы можете использовать следующее:

SELECT OBJECT_NAME(referencing_id) AS referencing_entity_name 
    ,referenced_server_name AS server_name
    ,referenced_database_name AS database_name
    ,referenced_schema_name AS schema_name
    , referenced_entity_name
FROM sys.sql_expression_dependencies 
WHERE referencing_id = OBJECT_ID(N'spMyStoredProcedure');

ОБНОВЛЕНО 2 : В комментарии к моему ответу Мартин Смит предложил использовать sys.sp_refreshsqlmodule вместо воссоздания хранимой процедуры. Так с кодом

SELECT 'EXEC sys.sp_refreshsqlmodule ''' + OBJECT_SCHEMA_NAME(object_id) +
              '.' + name + '''' FROM sys.objects WHERE type in (N'P', N'PC')

один получает скрипт, который можно использовать для проверки зависимостей хранимых процедур. Вывод будет выглядеть следующим образом (пример с AdventureWorks2008):

EXEC sys.sp_refreshsqlmodule 'dbo.uspGetManagerEmployees'
EXEC sys.sp_refreshsqlmodule 'dbo.uspGetWhereUsedProductID'
EXEC sys.sp_refreshsqlmodule 'dbo.uspPrintError'
EXEC sys.sp_refreshsqlmodule 'HumanResources.uspUpdateEmployeeHireInfo'
EXEC sys.sp_refreshsqlmodule 'dbo.uspLogError'
EXEC sys.sp_refreshsqlmodule 'HumanResources.uspUpdateEmployeeLogin'
EXEC sys.sp_refreshsqlmodule 'HumanResources.uspUpdateEmployeePersonalInfo'
EXEC sys.sp_refreshsqlmodule 'dbo.uspSearchCandidateResumes'
EXEC sys.sp_refreshsqlmodule 'dbo.uspGetBillOfMaterials'
EXEC sys.sp_refreshsqlmodule 'dbo.uspGetEmployeeManagers'
6 голосов
/ 10 декабря 2014

Вот что сработало для меня:

-- 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

Вы должны получить все недостающие зависимости для ваших SP, решая проблемы с поздним связыванием.

Исключение : is_caller_dependent = 1 не обязательно означает нарушенную зависимость.Это просто означает, что зависимость разрешается во время выполнения, потому что схема ссылочного объекта не указана.Вы можете избежать этого, указав схему ссылочного объекта (например, другого SP).

Кредиты на блог Джея и анонимный комментатор ...

2 голосов
/ 07 мая 2010

Мне нравится показывать примерный план выполнения. Он показывает много ошибок разумно, без необходимости запускать процесс.

1 голос
/ 12 марта 2019

Через девять лет после того, как я впервые задал этот вопрос, я только что обнаружил удивительный инструмент, созданный самими Microsoft, который может не только надежно проверять совместимость хранимых процедур между версиями SQL Server, но также и все другие внутренние аспекты. Его несколько раз переименовывали, но сейчас его называют:

Microsoft® Data Migration Assistant v4.2

https://www.microsoft.com/en-us/download/details.aspx?id=53595

Помощник по миграции данных (DMA) позволяет вам перейти на современную платформу данных, обнаружив проблемы совместимости, которые могут повлиять на функциональность базы данных в новой версии SQL Server. Он рекомендует повысить производительность и надежность для вашей целевой среды. Он позволяет вам не только перемещать вашу схему и данные, но и не содержащиеся в нем объекты с исходного сервера на целевой сервер.

Ответы выше, использующие EXEC sys.sp_refreshsqlmodule, были отличным началом, но мы столкнулись с одной ОСНОВНОЙ проблемой, которая выполнялась на 2008 R2: любая хранимая процедура или функция, которая была переименована (с использованием sp_rename, а не шаблон DROP / CREATE ) ОБРАТНО к своему предыдущему определению после выполнения процедуры обновления, поскольку внутренние метаданные не обновляются под новым именем. Это известная ошибка, исправленная в SQL Server 2012, но после этого у нас был веселый день восстановления. (Один из обходных путей, будущие читатели, - это выполнить ROLLBACK, если обновление вызывает ошибку.)

В любом случае, времена изменились, появились новые инструменты - и хорошие в этом отношении - таким образом, позднее добавление этого ответа.

1 голос
/ 28 октября 2011

Когда я наткнулся на этот вопрос, меня заинтересовал поиск безопасного, неинвазивного и быстрого метода проверки синтаксиса и ссылок на объекты (таблицы, столбцы).

Хотя я согласен, что фактическое выполнение каждой хранимой процедуры, скорее всего, вызовет больше проблем, чем просто их компиляция, следует соблюдать осторожность при первом подходе. То есть вам нужно знать, что на самом деле безопасно выполнять каждую хранимую процедуру (например, стирает ли она некоторые таблицы, например?). Эту проблему безопасности можно решить, обернув выполнение в транзакции и откатив ее назад, чтобы никакие изменения не были постоянными, как предложено в ответе 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
1 голос
/ 07 июля 2010

У меня была такая же проблема в предыдущем проекте, и я написал TSQL checker для SQL2005 и более поздних версий Windows-программу , реализующую те же функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...