在个人数据库服务器上,同一个实例下面挂载着许多相同结构的数据库,他们为不一样公司提供着服务,在许多时候我须要同时建立、修改、删除一些对象,存储过程就是其中一个,可是想要批量建立存储,这有些特殊,下面就教你如何实如今多个数据库中建立同一个存储过程(Create Same Stored Procedure in All Databases)。html
在以前的文章中屡次谈到使用游标的方式处理的各类问题:sql
若是使用游标来批量建立存储过程,可能你会遇到下面的一些问题,假设咱们须要在多个数据库(固然能够过滤掉部分数据库)中建立一样一个存储过程sp_GetId,存储过程的脚本以下Script1所示:数据库
-- Script1: -- 须要被批量建立的存储过程 USE [master] GO Create PROCEDURE [dbo].[sp_GetId] AS BEGIN DECLARE @database_id INT SET @database_id = 0 SELECT TOP 1 @database_id = [database_id] FROM sys.[databases] END
根据前面提到使用游标方式,咱们可能会写出相似下面的代码,错误代码Script2示例:服务器
-- Script2: -- ============================================= -- Author: <听风吹雨> -- Blog: <http://gaizai.cnblogs.com/> -- Create date: <2014/05/03> -- Description: <批量建立存储过程,错误代码示例> -- ============================================= DECLARE @databaseName VARCHAR(100) DECLARE @SQL NVARCHAR(MAX) DECLARE @itemCur CURSOR SET @itemCur = CURSOR FOR SELECT '['+[name]+']' FROM sys.databases WHERE database_id > 4 OPEN @itemCur FETCH NEXT FROM @itemCur INTO @databaseName WHILE @@FETCH_STATUS=0 BEGIN --逻辑处理 PRINT @databaseName SET @SQL = ' USE '+@databaseName+' GO CREATE PROCEDURE [dbo].[sp_GetId] AS BEGIN DECLARE @database_id INT SET @database_id = 0 SELECT TOP 1 @database_id = [database_id] FROM sys.[databases] END' PRINT(@SQL); EXEC(@SQL); FETCH NEXT FROM @itemCur INTO @databaseName END CLOSE @itemCur DEALLOCATE @itemCur
执行上面的代码你会遇到这样的错误信息:post
(Figure1:错误信息1)spa
根据错误信息修改上面的SQL代码,把”GO”改为“;”但仍是会出现下图Figure2的错误信息:code
(Figure2:错误信息2)htm
既然这样行不通,也许你还会尝试在[dbo].[sp_GetId]前面加上数据库名的变量,可是却出现下图Figure3的错误信息:对象
(Figure3:错误信息3)blog
上面的3个错误让咱们陷入了困境,也许你想过放弃了,可是通过努力,我经过2种方式实现了在多个数据库中建立同一个存储过程(你们可认为是批量建立存储过程),下面是实现的2种方式概述:
1. 经过修改过的系统存储过程[dbo].[sp_MSforeachdb_Filter]和拼接SQL;
2. 经过建立一个模板存储过程,由系统存储过程sp_MSForEachDB循环调用另一个建立存储过程的存储来建立模板存储过程(这也许听起来很拗口,看后面的实现脚本Script7,你就会理解了)。
(一) 实现方式1:经过修改过的系统存储过程[dbo].[sp_MSforeachdb_Filter]和拼接SQL;
1) 首先咱们须要在master数据库中建立一个存储过程[dbo].[sp_MSforeachdb_Filter],这是经过修改系统存储过程sp_MSforeachdb得来的,作的改进主要是能够过滤数据库,建立的SQL代码以下Script3所示:
-- Script3: -- ============================================= -- Author: <听风吹雨> -- Blog: <http://gaizai.cnblogs.com/> -- Create date: <2013.05.06> -- Description: <扩展sp_MSforeachdb,增长@whereand参数> -- ============================================= USE [master] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER OFF GO create proc [dbo].[sp_MSforeachdb_Filter] @command1 nvarchar(2000), @replacechar nchar(1) = N'?', @command2 nvarchar(2000) = null, @command3 nvarchar(2000) = null, @whereand nvarchar(2000) = null,@precommand nvarchar(2000) = null, @postcommand nvarchar(2000) = null as set deadlock_priority low /* This proc returns one or more rows for each accessible db, with each db defaulting to its own result set */ /* @precommand and @postcommand may be used to force a single result set via a temp table. */ /* Preprocessor won't replace within quotes so have to use str(). */ declare @inaccessible nvarchar(12), @invalidlogin nvarchar(12), @dbinaccessible nvarchar(12) select @inaccessible = ltrim(str(convert(int, 0x03e0), 11)) select @invalidlogin = ltrim(str(convert(int, 0x40000000), 11)) select @dbinaccessible = N'0x80000000' /* SQLDMODbUserProf_InaccessibleDb; the negative number doesn't work in convert() */ if (@precommand is not null) exec(@precommand) declare @origdb nvarchar(128) select @origdb = db_name() /* If it's a single user db and there's an entry for it in sysprocesses who isn't us, we can't use it. */ /* Create the select */ exec(N'declare hCForEachDatabase cursor global for select name from master.dbo.sysdatabases d ' + N' where (d.status & ' + @inaccessible + N' = 0)' + N' and (DATABASEPROPERTY(d.name, ''issingleuser'') = 0 and (has_dbaccess(d.name) = 1))' + @whereand) declare @retval int select @retval = @@error if (@retval = 0) exec @retval = sys.sp_MSforeach_worker @command1, @replacechar, @command2, @command3, 1 if (@retval = 0 and @postcommand is not null) exec(@postcommand) declare @tempdb nvarchar(258) SELECT @tempdb = REPLACE(@origdb, N']', N']]') exec (N'use ' + N'[' + @tempdb + N']') return @retval
2) 接着在master数据库中执行下面的SQL在多个数据库中建立同一个存储过程,实际上是把须要建立的存储过程经过拼接保存在@SQL变量中,使用[sp_MSforeachdb_Filter]来过滤数据库,并在符合条件的每一个数据库中执行@SQL中的语句,SQL代码以下Script4所示:
-- Script4: --批量建立存储过程 USE [master] GO DECLARE @SQL NVARCHAR(MAX) SELECT @SQL = COALESCE(@SQL,'') + ' USE [?]; EXEC('' CREATE PROCEDURE [dbo].[sp_GetId] AS BEGIN DECLARE @database_id INT SET @database_id = 0 SELECT TOP 1 @database_id = [database_id] FROM sys.[databases] END'')' PRINT @SQL --过滤数据库 EXEC [sp_MSforeachdb_Filter] @command1=@SQL, @whereand=" and [name] not in('tempdb','master','model','msdb') "
3) 执行上面的SQL脚本以后,除了('tempdb','master','model','msdb')4个数据库以外的数据库都会建立了存储过程sp_GetId,为了快速验证,可使用下面的SQL脚本进行验证:
-- Script5: -- 返回全部数据库sp_GetId存储过程列表 IF NOT EXISTS (SELECT * FROM [master].sys.objects WHERE object_id = OBJECT_ID(N'[master].[dbo].[SPList]') AND type in (N'U')) BEGIN CREATE TABLE [master].[dbo].[SPList]( [db_name] [sysname] NULL, [sp_name] [sysname] NULL, ) ON [PRIMARY] END ELSE TRUNCATE TABLE [master].[dbo].[SPList] EXEC [sp_MSforeachdb_Filter] @command1=' INSERT INTO [master].[dbo].[SPList]([sp_name]) SELECT [name] FROM [?].sys.[sysobjects] WHERE TYPE = ''P'' AND [name] = ''sp_GetId'' UPDATE [master].[dbo].[SPList] SET [db_name] = ''?'' WHERE [db_name] IS NULL ' SELECT * FROM [master].[dbo].[SPList]
执行上面的SQL脚本的结果以下图Figure4所示:
(Figure4:建立了sp_GetId存储过程的数据库列表)
(二) 实现方式2:经过建立一个模板存储过程,由系统存储过程sp_MSForEachDB循环调用另一个建立存储过程的存储来建立模板存储过程;
1) 为了能看到方式2的实际效果,咱们须要把存在sp_GetId存储过程的数据库中批量删除这个存储过程,经过下面的脚本Script6来实现:
-- Script6: -- 批量删除存储过程 USE [master] GO DECLARE @SQL NVARCHAR(MAX) SELECT @SQL = COALESCE(@SQL,'') + ' USE [?]; EXEC('' IF EXISTS (SELECT * FROM sys.[objects] WHERE object_id = OBJECT_ID(N''''[dbo].[sp_GetId]'''') AND type in (N''''P'''', N''''PC'''')) DROP PROCEDURE [dbo].[sp_GetId] '')' PRINT @SQL --过滤数据库 EXEC [sp_MSforeachdb_Filter] @command1=@SQL, @whereand=" and [name] not in('tempdb','master','model','msdb') "
2) 经过Script5确认全部数据库都不存在sp_GetId存储过程;
3) 接着在master数据库中建立模板存储过程sp_GetId,建立脚本如Script1所示,这里不作重复;
4) 再接着建立一个存储过程CreateProcedure,这个存储过程的做用就是建立存储过程,在这个存储过程CreateProcedure利用系统表返回sp_GetId存储过程的内容,保存在变量@proc_text中,查询出以下所示:
-- Script7: --建立存储过程的存储过程 USE [master] GO -- ============================================= -- Author: <听风吹雨> -- Blog: <http://gaizai.cnblogs.com/> -- Create date: <2014.05.06> -- Description: <建立存储过程的存储过程> -- ============================================= CREATE PROC CreateProcedure ( @dbname SYSNAME, @spname SYSNAME ) AS BEGIN SELECT @dbname = REPLACE(REPLACE(@dbname,'[',''),']','') IF @dbname <> 'master' BEGIN DECLARE @proc_text NVARCHAR(MAX) SELECT @proc_text = REPLACE([text],'''','''''') FROM [sysobjects] o INNER JOIN [syscomments] c ON c.id = o.id WHERE o.type = 'P' AND o.name = @spname DECLARE @sql NVARCHAR(MAX) SET @sql = 'USE [' + @dbname + ']; EXEC ('' ' + @proc_text + ''');' EXEC SP_EXECUTESQL @sql END END GO
5) 准备完上面的步骤,只须要下面的一条SQL语句就能批量建立存储过程sp_GetId:
-- Script8: -- 批量建立存储过程 USE [master] GO --过滤数据库 EXEC [sp_MSforeachdb_Filter] @command1='CreateProcedure ''[?]'', ''sp_GetId''', @whereand=" and [name] not in('tempdb','master','model','msdb') "
执行上面的SQL脚本的结果以下图Figure5所示,与Figure4的区别就是在master数据库中多了一个模板存储过程sp_GetId。
(Figure5:建立了sp_GetId存储过程的数据库列表)
上面已经经过两种方式实现了在多个数据库中建立同一个存储过程,若是存储过程sp_GetId属于比较简单的,使用方式1实现会比较快捷,若是sp_GetId比较复杂了,好比存储过程里面还包含单引号或者代码比较多的状况下,建议使用方式2,虽然方式2的步骤会多一点,可是只要建立好模板存储过程,其它的根本不会由于存储过程sp_GetId而变得复杂;
在实际运用中,不少时候你须要的并不仅仅是在多个数据库中建立同一个存储过程,可能还须要修改同一个存储过程,经过上面的阅读你也许猜到修改存储过程,能够先删除,再建立,对的,这是没有问题的,不过也能够直接修改,下面提供SQL代码:
1) 首先修改下master数据库的模板存储过程sp_GetId,在存储过程里面中加入一个变量@id:
-- Script10: -- 须要被批量建立的存储过程 USE [master] GO Create PROCEDURE [dbo].[sp_GetId] AS BEGIN DECLARE @database_id INT --修改部分,增长了一个变量 DECLARE @id INT SET @database_id = 0 SELECT TOP 1 @database_id = [database_id] FROM sys.[databases] END
2) 接着建立一个修改存储过程的存储过程AlterProcedure,只须要把变量@proc_text里面的“CREATE PROC”替换成“ALTER PROC”就能够了:
-- Script10: --修改存储过程的存储过程 USE [master] GO -- ============================================= -- Author: <听风吹雨> -- Blog: <http://gaizai.cnblogs.com/> -- Create date: <2014.05.06> -- Description: <修改存储过程的存储过程> -- ============================================= CREATE PROC AlterProcedure ( @dbname SYSNAME, @spname SYSNAME ) AS BEGIN SELECT @dbname = REPLACE(REPLACE(@dbname,'[',''),']','') IF @dbname <> 'master' BEGIN DECLARE @proc_text NVARCHAR(MAX) SELECT @proc_text = REPLACE([text],'''','''''') FROM [sysobjects] o INNER JOIN [syscomments] c ON c.id = o.id WHERE o.type = 'P' AND o.name = @spname DECLARE @sql NVARCHAR(MAX) SET @proc_text = REPLACE(@proc_text,'CREATE PROC','ALTER PROC') SET @sql = 'USE [' + @dbname + ']; EXEC ('' ' + @proc_text + ''');' EXEC SP_EXECUTESQL @sql END END GO
3) 准备完上面的步骤,再把Script8的脚本中调用存储过程CreateProcedure改为调用存储过程AlterProcedure,经过下面的一条SQL语句批量修改存储过程sp_GetId:
-- Script11: -- 批量修改存储过程 USE [master] GO --过滤数据库 EXEC [sp_MSforeachdb_Filter] @command1='AlterProcedure ''[?]'', ''sp_GetId''', @whereand=" and [name] not in('tempdb','master','model','msdb') "
4) 建立完成后,剩下的就是验证下数据库中存储过程sp_GetId的内容了;
Create Same Stored Procedure on All Databases using sp_MSForEachDB T-SQL Example