【问题标题】:How to Check all stored procedure is ok in sql server?如何在sql server中检查所有存储过程是否正常?
【发布时间】:2011-03-02 22:15:43
【问题描述】:

如果我删除表或字段,如何在 sql server 中检查所有存储过程是否正常?

【问题讨论】:

    标签: sql sql-server dependencies metadata


    【解决方案1】:

    给出的答案都无法找到重命名或删除表导致的错误
    但请高兴,我在 SQL Server 2017 及更高版本上有一个解决方案:

    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 在我的sys.dm_sql_referenced_entities 中似乎不存在,我用is_all_columns_found 尝试了你的脚本,但它返回的误报太多...
    • 这似乎效果更好: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))
    • 我在 SQL Server 2017 上编写并测试了这个脚本
    【解决方案2】:

    我的方法有点不同。我在 SSMS 中为一堆 procs 创建了更改脚本,然后等待了几秒钟,以便 SSMS 处理它们并得到我想要的:

    然后 SSMS 右边框为任何错误行添加一个红点,我可以轻松检查、更正并稍后执行相同的脚本以使用正确的值进行更新。

    【讨论】:

      【解决方案3】:

      一旦我对表进行了更改(例如列重命名),我必须更改引用表列的所有存储过程、函数和视图。显然,我必须一一手动更改它们。但是我的数据库包含数百个这样的对象。所以我想确保我已经改变了所有依赖的对象。一种解决方案是重新编译所有对象(通过脚本)。但是重新编译只发生在每个对象的下一次执行时。但我想要的是验证它们并立即获取详细信息。

      为此,我可以使用“sp_refreshsqlmodule”而不是“sp_recompile”。如果解析不正确,这将刷新每个对象并引发错误。 这是下面的脚本;

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

      如果任何对象抛出错误,我现在可以处理它并手动修复它的问题。

      【讨论】:

        【解决方案4】:

        相同的想法,但更通用 - 您检查所有用户定义的对象与主体 它会在编译过程中向您显示错误。这在重命名/删除对象/列等后非常有用

        只需在数据库架构更新后运行它以确保所有主体对象仍然有效

        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
        

        【讨论】:

        • 是的,这样更好,因为它实际上显示了原始错误。
        • 它没有识别已使用的已删除表。
        【解决方案5】:

        我基本上做了同样的事情,但写成无 CURSORless 超级快。

        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
        

        【讨论】:

        • 使用 while 语句循环遍历数据表与使用带有 fast_forward 的游标基本相同。两者都在痛苦地排(RBAR)。这是一种可以接受的情况,因为它不能被基于集合的操作替换。
        • 这确实比使用光标好一点,即使它仍然使用 while 循环。
        【解决方案6】:

        我尝试了“Cade Roux”答案,它出错了,我将其修复如下

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

        【讨论】:

          【解决方案7】:

          除了 Michael Petito 的脚本之外,您还可以像这样检查 SP(延迟名称解析)中的后期绑定对象的问题:

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

          【讨论】:

          • 问题是它仍然会返回 CTE 别名和临时表作为空引用。
          • 这会拾取通过 sp_refreshsqlmodule 的 procs,即使其中的对象无效
          【解决方案8】:

          我发现 Cade 的答案在制定我自己的脚本以检查数据库中的对象时很有用,所以我想我也会分享我的脚本:

          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;
          

          【讨论】:

          • 刷新触发器的唯一方法是在未加密的情况下更改它们。 (connect.microsoft.com/SQLServer/feedback/details/261905/…)。所以上面的脚本在遇到触发器时会给出误报
          • 不错的帖子,完美!另外,您必须检查对象是否是模式绑定的,因为 sp_refreshsqlmodule 会给出假阴性,因为不支持模式绑定对象。因此,在您的查询中添加以下内容:and isnull(objectproperty(o.object_id,'IsSchemaBound'),0)=0 ;
          • 我还将第 6 行更改为 SELECT SCHEMA_NAME(o.schema_id) + '.[' + OBJECT_NAME(o.object_id) + ']',以防您的存储过程名称中带有点。
          • @DrewFreyling 好主意!您也可以使用QUOTENAME 来引用对象名称。我会更新答案。
          • 我在一个引用不存在的表的存储过程上尝试了这个脚本,但它没有突出显示它。果然,如果我只是运行 sp_refreshsqlmodule 'mysp' 它也不会报告这个存储过程。
          【解决方案9】:

          它不会捕获所有内容(动态 SQL 或后期绑定对象),但它可能很有用 - 在所有非模式绑定存储过程上调用 sp_refreshsqlmodule(您可以在之前调用它以确保更新依赖关系,然后查询依赖关系,或者之后调用它,看看是否有任何问题):

          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
                      )
          

          【讨论】:

          • @Martin 是的,我应该提到这一点。在我的情况下,因为它是一个 ALTER 并且没有另一个使用相同名称的过程,所以我收到了一个错误,它没有造成任何伤害。
          • 完美运行,节省了我很多时间
          • 我刚刚发现一个 proc 引用了一个不存在的数据库,它保存 OK 并通过 sp_refreshsqlmodule
          【解决方案10】:

          想到的几种方法

          1. 运行程序的最明显方式
          2. 在删除表或字段之前检查表的依赖关系。然后查看那些依赖的proceudres
          3. 在所有过程中生成脚本并搜索该字段或表
          4. 查询系统对象

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-08
            • 1970-01-01
            • 1970-01-01
            • 2015-06-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多