【问题标题】:Syntax check all stored procedures?语法检查所有存储过程?
【发布时间】:2009-07-24 13:27:14
【问题描述】:

我想确保所有存储过程在语法上仍然有效。 (如果有人重命名/删除表/列,就会发生这种情况)。

现在我检查所有存储过程语法的解决方案是进入企业管理器,选择列表中的第一个存储过程,然后使用该过程:

  1. 输入
  2. Alt+C
  3. 逃跑
  4. 逃跑
  5. 向下箭头
  6. 转到 1

它有效,但它非常乏味。我想要一个名为

的存储过程

SyntaxCheckAllStoredProcedures

就像我编写的其他存储过程一样,它对视图执行相同的操作:

刷新所有视图


为了大家的利益,RefreshAllViews:

RefreshAllViews.prc

CREATE PROCEDURE dbo.RefreshAllViews AS

-- This sp will refresh all views in the catalog. 
--     It enumerates all views, and runs sp_refreshview for each of them

DECLARE abc CURSOR FOR
     SELECT TABLE_NAME AS ViewName
     FROM INFORMATION_SCHEMA.VIEWS
OPEN abc

DECLARE @ViewName varchar(128)

-- Build select string
DECLARE @SQLString nvarchar(2048)

FETCH NEXT FROM abc 
INTO @ViewName
WHILE @@FETCH_STATUS = 0 
BEGIN
    SET @SQLString = 'EXECUTE sp_RefreshView '+@ViewName
    PRINT @SQLString
    EXECUTE sp_ExecuteSQL @SQLString

    FETCH NEXT FROM abc
    INTO @ViewName
END
CLOSE abc
DEALLOCATE abc

为了每个人的利益,存储过程将所有存储过程标记为需要重新编译(将存储过程标记为重新编译不会告诉您它是否在语法上有效):

重新编译AllStoredProcedures.prc

CREATE PROCEDURE dbo.RecompileAllStoredProcedures AS

DECLARE abc CURSOR FOR
     SELECT ROUTINE_NAME
     FROM INFORMATION_SCHEMA.routines
    WHERE ROUTINE_TYPE = 'PROCEDURE'
OPEN abc

DECLARE @RoutineName varchar(128)

-- Build select string once 
DECLARE @SQLString nvarchar(2048)

FETCH NEXT FROM abc 
INTO @RoutineName
WHILE @@FETCH_STATUS = 0 
BEGIN
    SET @SQLString = 'EXECUTE sp_recompile '+@RoutineName
    PRINT @SQLString
    EXECUTE sp_ExecuteSQL @SQLString

    FETCH NEXT FROM abc
    INTO @RoutineName
END
CLOSE abc
DEALLOCATE abc

为了完整起见,UpdateAllStatistics 过程。这将通过执行完整的数据扫描来更新数据库中的所有统计信息:

RefreshAllStatistics.prc

CREATE PROCEDURE dbo.RefreshAllStatistics AS

EXECUTE sp_msForEachTable 'UPDATE STATISTICS ? WITH FULLSCAN'

【问题讨论】:

  • 请注意您的标记。这是标记为“sqlserver”的站点上的唯一 问题。请改用“sql-server”。
  • 您提议的程序将一些指向已重命名字段的程序视为“已成功标记为重新编译”。
  • @SergioPrats 是的。作为替代方案,我有应用程序代码 i) 从 sql server 获取存储过程定义片段 ii) 组装完整的CREATE PROCEDURE sql iii) 在 iv) 上设置 no_exec 尝试更改存储过程。数据库将给出错误 v) 应用程序捕获错误并显示单元测试失败。

标签: sql-server stored-procedures maintenance


【解决方案1】:

您也可以“就地”执行此操作 - 无需获取所有创建语句。

除了设置NOEXEC ON,你还需要设置你喜欢的SHOWPLAN_* ON(我用SHOWPLAN_TEXT)。现在您可以摆脱第 2 步,只需执行您在第 1 步中检索到的每个过程。

这是一个使用单个存储过程的示例。你可以把它加入你最喜欢的循环中:

create procedure tests @bob int as 
select * from missing_table_or_view
go 

set showplan_text on; 
go 

set noexec on 

exec tests 

set noexec off
go 
set showplan_text off; 
go 
drop procedure tests 
go

以上示例应生成以下输出:

消息 208,级别 16,状态 1,程序测试,第 2 行
对象名称“missing_table_or_view”无效。

【讨论】:

  • 有没有理由为什么要set showplan_text on; 但后来set showplan_XML off; 而不是set showplan_text off;
  • 只是这个 10 年前的答案中的一个错字。更正为set showplan_text off;
【解决方案2】:

KenJ 建议的检查绝对是最好的检查,因为重新创建/更改方法不会发现所有错误。例如。

  • 由于查询提示而无法执行计划
  • 我什至有一个 SP 引用了一个不存在的表,该表在没有检测到错误的情况下通过。

请找到我的版本,它可以使用下面的 KenJ 方法一次检查所有现有 SP。 AFAIK,它会检测每一个会阻止 SP 执行的错误。

--Forces the creation of execution-plans for all sps.
--To achieve this, a temporary SP is created that calls all existing SPs.
--It seems like the simulation of the parameters is not necessary. That makes things a lot easier.
DECLARE @stmt NVARCHAR(MAX) = 'CREATE PROCEDURE pTempCompileTest AS ' + CHAR(13) + CHAR(10)
SELECT @stmt = @stmt + 'EXEC [' + schemas.name + '].[' + procedures.name + '];'
    FROM sys.procedures
        INNER JOIN sys.schemas ON schemas.schema_id = procedures.schema_id
    WHERE schemas.name = 'dbo'
    ORDER BY procedures.name

EXEC sp_executesql @stmt
GO

--Here, the real magic happens.
--In order to display as many errors as possible, XACT_ABORT is turned off.
--Unfortunately, for some errors, the execution stops anyway.
SET XACT_ABORT OFF
GO
--Showplan disables the actual execution, but forces t-sql to create execution-plans for every statement.
--This is the core of the whole thing!
SET SHOWPLAN_ALL ON
GO
--You cannot use dynamic SQL in here, since sp_executesql will not be executed, but only show the string passed in in the execution-plan
EXEC pTempCompileTest
GO
SET SHOWPLAN_ALL OFF
GO
SET XACT_ABORT ON
GO
--drop temp sp again
DROP PROCEDURE pTempCompileTest
--If you have any errors in the messages-window now, you should fix these...

【讨论】:

    【解决方案3】:

    如果您使用的是 sql 2008 r2 或更低版本,请不要使用

    设置 NOEXEC 开启

    它只检查语法而不是潜在的错误,比如表或列的存在。 而是使用:

    打开 FMTONLY

    它会在尝试返回存储过程的元数据时进行完整编译。

    对于 2012 年,您将需要使用存储过程: sp_describe_first_result_set

    您还可以在 Tsql 中编写一个完整的脚本来检查所有 sp 和视图,这只是一点工作。

    更新 我为 in tsql 编写了一个完整的解决方案,它遍历所有用户定义的存储过程并检查语法。脚本冗长但可以在这里找到http://chocosmith.wordpress.com/2012/12/07/tsql-recompile-all-views-and-stored-proceedures-and-check-for-error/

    【讨论】:

    • 有人用TRUNCATE TABLE CustomerAccounts 尝试过SET FMTONLY ON(或sp_describe_first_result_set)吗?
    • @ChocoSmith 对于存储过程,使用 sys.sp_refreshsqlmodule 有缺点吗?我发现它会捕获不符合表或架构更改的存储过程。
    • @Question3CPO 出色的捕捉/点,fmtonly 将检查底层架构,但 sp_refreshsqlmodule 将检查当前架构。 sp_refreshsqlmodule 也适用于视图(与在视图上调用 sp_refreshview 相同)。 sp_refreshsqlmodule 有点慢,但我认为在存储过程中断时速度不应该受到关注:)。今晚我会检查它,看看更新是否破坏了任何东西。
    • @ChocoSmith 感谢您的回复。很高兴知道以供将来参考。
    • @Ian Boyd 有点延迟,但是......如果你打开了 fmtonly,你不会截断表格
    【解决方案4】:

    此外,您可能需要考虑使用Visual Studio Team System 2008 Database Edition,其中包括在构建时对项目中的所有存储过程进行静态验证,从而确保所有存储过程与当前架构一致。

    【讨论】:

    • 只有在您使用 Visual Studio 或针对 SQL Server 2008 数据库进行开发时才有效?
    • @Ian 是的,它只适用于 Visual Studio,因为它将使用特定的数据库项目类型(更准确地说,您需要 VS Team System),但它也适用于 Sql Server 2005
    【解决方案5】:

    我知道这已经很老了,但我创建了一个稍微不同的版本,它实际上重新创建了所有存储过程,因此如果它们无法编译就会抛出错误。这是使用 SP_Recompile 命令无法实现的。

    CREATE PROCEDURE dbo.UTL_ForceSPRecompilation
    (
        @Verbose BIT = 0
    )
    AS
    BEGIN
    
        --Forces all stored procedures to recompile, thereby checking syntax validity.
    
        DECLARE @SQL NVARCHAR(MAX)
        DECLARE @SPName NVARCHAR(255)           
    
        DECLARE abc CURSOR FOR
             SELECT NAME, OBJECT_DEFINITION(o.[object_id])
             FROM sys.objects AS o 
             WHERE o.[type] = 'P'
             ORDER BY o.[name]
    
        OPEN abc
    
        FETCH NEXT FROM abc
        INTO @SPName, @SQL
        WHILE @@FETCH_STATUS = 0 
        BEGIN       
    
            --This changes "CREATE PROCEDURE" to "ALTER PROCEDURE"
            SET @SQL = 'ALTER ' + RIGHT(@SQL, LEN(@SQL) - (CHARINDEX('CREATE', @SQL) + 6))
    
            IF @Verbose <> 0 PRINT @SPName
    
            EXEC(@SQL)
    
            FETCH NEXT FROM abc
            INTO @SPName, @SQL
        END
        CLOSE abc
        DEALLOCATE abc  
    
    END
    

    【讨论】:

    • 工作正常,但也需要处理CREATE PROC
    【解决方案6】:

    我知道这是一个老问题,但当我找不到任何合适的时候,这是我的解决方案。

    在对数据库进行大量更改后,我需要验证我的存储过程和视图。

    基本上我想要的是尝试使用当前过程和视图(实际上并未更改它们)来执行 ALTER PROCEDURE 和 ALTER VIEW。

    我写的这个很好用。

    注意!不要在实时数据库上执行,制作副本以验证然后修复需要修复的东西。 sys.sql_modules 也可能不一致,所以要格外小心。我不使用它来实际进行更改,只是检查哪些工作不正常。

    DECLARE @scripts TABLE
    (
        Name NVARCHAR(MAX),
        Command NVARCHAR(MAX),
        [Type] NVARCHAR(1)
    )
    
    DECLARE @name NVARCHAR(MAX),        -- Name of procedure or view
        @command NVARCHAR(MAX),         -- Command or part of command stored in syscomments
        @type NVARCHAR(1)               -- Procedure or view
    
    INSERT INTO @scripts(Name, Command, [Type])
    SELECT P.name, M.definition, 'P' FROM sys.procedures P 
    JOIN sys.sql_modules M ON P.object_id = M.object_id
    
    INSERT INTO @scripts(Name, Command, [Type])
    SELECT V.name, M.definition, 'V' FROM sys.views V 
    JOIN sys.sql_modules M ON V.object_id = M.object_id
    
    DECLARE curs CURSOR FOR
    SELECT Name, Command, [Type]  FROM @scripts
    
    OPEN curs
    
    FETCH NEXT FROM curs
    INTO @name, @command, @type
    
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        BEGIN TRY
            IF @type = 'P'
                SET @command = REPLACE(@command, 'CREATE PROCEDURE', 'ALTER PROCEDURE')
            ELSE
                SET @command = REPLACE(@command, 'CREATE VIEW', 'ALTER VIEW')
    
    
            EXEC sp_executesql @command
            PRINT @name + ' - OK'
        END TRY
        BEGIN CATCH
            PRINT @name + ' - FAILED: ' + CAST(ERROR_NUMBER() AS NVARCHAR(MAX)) + ' ' + ERROR_MESSAGE()
            --PRINT @command
        END CATCH
    
        FETCH NEXT FROM curs
        INTO @name, @command, @type
    END
    
    CLOSE curs 
    

    【讨论】:

      【解决方案7】:

      有点冗长的选择:

      1. 创建数据库的副本 (备份还原)。如果您的置信度很高,您可以在目标数据库上执行此操作。
      2. 使用 SSMS 编写所有 存储过程到单个脚本文件中
      3. 删除所有程序
      4. 运行脚本重新创建它们。任何无法创建的都会出错。

      这里有几个棘手的问题,例如:

      • 您想要“如果 proc 存在 然后删除 proc GO 创建 proc ... GO" 分隔每个过程的语法。
      • 如果嵌套过程将失败 调用尚未调用的 proc (重新)创建。运行脚本几次 时代应该抓住这一点(因为 正确订购它们可能是真实的 痛)。
      • 可能会出现其他更模糊的问题,因此请小心。

      要快速删除 10 或 1000 个过程,请运行

      SELECT 'DROP PROCEDURE ' + schema_name(schema_id) + '.' +  name
       from sys.procedures
      

      选择输出,然后运行它。

      这假设您正在执行一项非常罕见的任务。如果您必须定期(每天、每周...)执行此操作,请告诉我们原因!

      【讨论】:

      • 是的 - 我不可能在实时数据库中删除存储过程;)
      【解决方案8】:

      没有办法从 T-SQL 或企业管理器中做到这一点,所以我不得不从客户端代码中编写一些东西。我不会在这里发布所有代码,但诀窍是:

      1) 获取所有存储过程的列表

       SELECT ROUTINE_NAME AS StoredProcedureName
       FROM INFORMATION_SCHEMA.ROUTINES
       WHERE ROUTINE_TYPE = 'PROCEDURE' --as opposed to a function
       ORDER BY ROUTINE_NAME
      

      2) 获取存储过程创建T-SQL:

      select
         c.text
      from dbo.syscomments c
      where c.id = object_id(N'StoredProcedureName')
      order by c.number, c.colid
      option(robust plan)
      

      3) 在开启 NOEXEC 的情况下运行 create 语句,以便检查语法,但实际上并不会尝试创建存储过程:

      connection("SET NOEXEC ON", ExecuteNoRecords);
      connection(StoredProcedureCreateSQL, ExecuteNoRecords);
      connection("SET NOEXEC ON", ExecuteNoRecords);
      

      【讨论】:

      • 决定接受您的挑战 :D 不确定我们是否可以自行插入博客,但我在博客上发布了完整的解决方案。原来是安静了很久。 link 虽然我还没有检查过 truncate table 但我可以看到这是一个问题。不过,我认为管理工作室也没有做到这一点。
      【解决方案9】:

      这是一个处理多个模式的修正案

      SET ANSI_NULLS ON
      GO
      
      SET QUOTED_IDENTIFIER ON
      GO
      
      ALTER PROCEDURE [dbo].[RefreshAllViews] AS
      
      -- This sp will refresh all views in the catalog. 
      --     It enumerates all views, and runs sp_refreshview for each of them
      
      DECLARE abc CURSOR FOR
           SELECT TABLE_SCHEMA+'.'+TABLE_NAME AS ViewName
           FROM INFORMATION_SCHEMA.VIEWS
      OPEN abc
      
      DECLARE @ViewName varchar(128)
      
      -- Build select string
      DECLARE @SQLString nvarchar(2048)
      
      FETCH NEXT FROM abc 
      INTO @ViewName
      WHILE @@FETCH_STATUS = 0 
      BEGIN
          SET @SQLString = 'EXECUTE sp_RefreshView ['+@ViewName+']'
          PRINT @SQLString
          EXECUTE sp_ExecuteSQL @SQLString
      
          FETCH NEXT FROM abc
          INTO @ViewName
      END
      CLOSE abc
      DEALLOCATE abc
      GO
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-16
        • 1970-01-01
        • 2015-12-28
        • 2010-09-18
        • 2010-10-29
        相关资源
        最近更新 更多