SQL Server 在多个数据库中创建同一个存储过程(Create Same Stored Procedure in All Databases)

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介: 原文:SQL Server 在多个数据库中创建同一个存储过程(Create Same Stored Procedure in All Databases)一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 遇到的问题(Problems)...
原文: SQL Server 在多个数据库中创建同一个存储过程(Create Same Stored Procedure in All Databases)

一.本文所涉及的内容(Contents)

  1. 本文所涉及的内容(Contents)
  2. 背景(Contexts)
  3. 遇到的问题(Problems)
  4. 实现代码(SQL Codes)
    1. 方法一:拼接SQL;
    2. 方法二:调用模板存储过程创建存储过程;
    3. 总结
    4. 扩展阅读
  5. 参考文献(References)

二.背景(Contexts)

  在我的数据库服务器上,同一个实例下面挂载着许多相同结构的数据库,他们为不同公司提供着服务,在许多时候我需要同时创建、修改、删除一些对象,存储过程就是其中一个,但是想要批量创建存储,这有些特殊,下面就教你如何实现在多个数据库中创建同一个存储过程(Create Same Stored Procedure in All Databases)。

三.遇到的问题(Problems)

  在之前的文章中多次谈到使用游标的方式处理的各种问题:

  如果使用游标来批量创建存储过程,可能你会遇到下面的一些问题,假设我们需要在多个数据库(当然可以过滤掉部分数据库)中创建同样一个存储过程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

执行上面的代码你会遇到这样的错误信息:

wps_clip_image-14323

(Figure1:错误信息1)

根据错误信息修改上面的SQL代码,把”GO”改成“;”但还是会出现下图Figure2的错误信息:

wps_clip_image-8651

(Figure2:错误信息2)

既然这样行不通,也许你还会尝试在[dbo].[sp_GetId]前面加上数据库名的变量,但是却出现下图Figure3的错误信息:

wps_clip_image-17048

(Figure3:错误信息3)

四.实现代码(SQL Codes)

  上面的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所示:

wps_clip_image-6113

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

wps_clip_image-14563

(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的内容了;

五.参考文献(References)

Create Same Stored Procedure on All Databases using sp_MSForEachDB T-SQL Example

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
11天前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
11天前
|
SQL 存储 Linux
从配置源到数据库初始化一步步教你在CentOS 7.9上安装SQL Server 2019
【11月更文挑战第8天】本文介绍了在 CentOS 7.9 上安装 SQL Server 2019 的详细步骤,包括系统准备、配置安装源、安装 SQL Server 软件包、运行安装程序、初始化数据库以及配置远程连接。通过这些步骤,您可以顺利地在 CentOS 系统上部署和使用 SQL Server 2019。
|
12天前
|
SQL 存储 Linux
从配置源到数据库初始化一步步教你在CentOS 7.9上安装SQL Server 2019
【11月更文挑战第7天】本文介绍了在 CentOS 7.9 上安装 SQL Server 2019 的详细步骤,包括系统要求检查与准备、配置安装源、安装 SQL Server 2019、配置 SQL Server 以及数据库初始化(可选)。通过这些步骤,你可以成功安装并初步配置 SQL Server 2019,进行简单的数据库操作。
|
22天前
|
SQL 数据采集 监控
局域网监控电脑屏幕软件:PL/SQL 实现的数据库关联监控
在当今网络环境中,基于PL/SQL的局域网监控系统对于企业和机构的信息安全至关重要。该系统包括屏幕数据采集、数据处理与分析、数据库关联与存储三个核心模块,能够提供全面而准确的监控信息,帮助管理者有效监督局域网内的电脑使用情况。
16 2
|
27天前
|
SQL JSON Java
没有数据库也能用 SQL
SPL(Structured Process Language)是一款开源软件,允许用户直接对CSV、XLS等文件进行SQL查询,无需将数据导入数据库。它提供了标准的JDBC驱动,支持复杂的SQL操作,如JOIN、子查询和WITH语句,还能处理非标准格式的文件和JSON数据。SPL不仅简化了数据查询,还提供了强大的计算能力和友好的IDE,适用于多种数据源的混合计算。
|
29天前
|
SQL 数据库
SQL数据库基础语法入门
[link](http://www.vvo.net.cn/post/082935.html)
|
2月前
|
SQL 数据库
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
SQL Server附加数据库出现错误823,附加数据库失败。数据库没有备份,无法通过备份恢复数据库。 SQL Server数据库出现823错误的可能原因有:数据库物理页面损坏、数据库物理页面校验值损坏导致无法识别该页面、断电或者文件系统问题导致页面丢失。
103 12
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
|
26天前
|
存储 数据挖掘 数据库
数据库数据恢复—SQLserver数据库ndf文件大小变为0KB的数据恢复案例
一个运行在存储上的SQLServer数据库,有1000多个文件,大小几十TB。数据库每10天生成一个NDF文件,每个NDF几百GB大小。数据库包含两个LDF文件。 存储损坏,数据库不可用。管理员试图恢复数据库,发现有数个ndf文件大小变为0KB。 虽然NDF文件大小变为0KB,但是NDF文件在磁盘上还可能存在。可以尝试通过扫描&拼接数据库碎片来恢复NDF文件,然后修复数据库。
|
2月前
|
SQL 关系型数据库 MySQL
创建包含MySQL和SQLServer数据库所有字段类型的表的方法
创建一个既包含MySQL又包含SQL Server所有字段类型的表是一个复杂的任务,需要仔细地比较和转换数据类型。通过上述方法,可以在两个数据库系统之间建立起相互兼容的数据结构,为数据迁移和同步提供便利。这一过程不仅要考虑数据类型的直接对应,还要注意特定数据类型在不同系统中的表现差异,确保数据的一致性和完整性。
35 4
|
2月前
|
SQL 存储 数据管理
SQL Server数据库
SQL Server数据库
59 11
下一篇
无影云桌面