将视图从当前数据库复制到另一个数据库的存储过程

Stored Procedure to Copy Views from Current Database to Another

有人问我是否可以创建一个存储过程,将当前数据库中的所有视图复制到另一个数据库(通过存储过程参数命名)。

就上下文而言,所有数据库都具有相同的模式。这种情况的出现要归功于第 3 方风险建模工具,该工具将每个 运行 的输出生成为一个全新的数据库(而不是现有数据库中的附加行)。用户想要一种简单的方法来按需将他们的大约 20 个自定义视图(从他们的“模板”数据库)“应用”到另一个相同的数据库。他们希望在一个数据库中维护视图的“最新版本”,然后通过执行此存储过程在任何其他数据库上“更新”(删除 + 创建)视图。据我所知,这个问题与 Copy a view definition from one database to another one in SQL Server 中的问题几乎相同,后者从未得到答案。


到目前为止我所取得的成绩:

所以目前我的概念验证解决方案如下所示:

ALTER PROCEDURE [dbo].[usp_Enhance_Database_With_Views]
    @TargetDatabase SYSNAME,
AS
    IF DB_ID(@TargetDatabase) IS NULL  /*Validate the database name exists*/
    BEGIN
       RAISERROR('Invalid Database Name passed',16,1)
       RETURN
    END
    
    DECLARE @CreateViewStatement NVARCHAR(MAX) = '
        DECLARE @ViewDefinition NVARCHAR(MAX);
        SELECT @ViewDefinition = definition FROM sys.sql_modules 
        WHERE [object_id] = OBJECT_ID(''dbo.SampleView'');
        EXEC ' + QUOTENAME(@TargetDatabase) + '.dbo.sp_executesql @ViewDefinition'
    EXEC (@CreateViewStatement);

我在网上找不到其他类似的东西,但令人惊讶的是(对我来说)它有效。 “SampleView”被复制到新数据库。我现在可以扩展这个概念以复制所有视图。但在我继续之前...


我错过了吗?是否有不包括在另一个动态 SQL 中构建和执行动态 SQL 的存储过程解决方案?

Have I missed the mark here? Is there a stored procedure solution that doesn't include building and executing dynamic SQL within another dynamic SQL?

没有。如果你想用TSQL做这个,就是这样。

我想我找到了一种稍微好一点的方法(当我增加这个例程的复杂性时,它会产生更大的不同)。

我最初实现的主要问题:

DECLARE @CreateViewStatement NVARCHAR(MAX) = '
    DECLARE @ViewDefinition NVARCHAR(MAX);
    SELECT @ViewDefinition = definition FROM sys.sql_modules 
        WHERE [object_id] = OBJECT_ID(''dbo.SampleView'');
    EXEC ' + QUOTENAME(@TargetDatabase) + '.dbo.sp_executesql @ViewDefinition'
EXEC (@CreateViewStatement);

dbo.sp_executesql @ViewDefinition的内部动态执行需要在动态SQL块中导出视图创建代码@ViewDefinition。这段代码现在很简单(提取一个硬编码的视图名称)——但我的计划是对此进行扩展以遍历数据库中的所有视图并将它们全部复制。我宁愿不必在动态 SQL 块中编写此逻辑。

理想情况下,我想让这个逻辑存在于主存储过程 space 中,并且只将生成的 CREATE VIEW 命令传递给动态 SQL,但只是将definition 返回的 CREATE VIEW 命令会导致引号转义问题。解决方法是手动转义返回的 SQL 代码,以便随后 'unescaped' 由内部动态 SQL 执行:

DECLARE @ViewDefinition NVARCHAR(MAX);
SELECT @ViewDefinition = definition FROM sys.sql_modules 
    WHERE [object_id] = OBJECT_ID('dbo.SampleView');

DECLARE @CreateViewStatement NVARCHAR(MAX) = '
    DECLARE @SQL NVARCHAR(MAX) = ''' + REPLACE(@ViewDefinition,'''','''''') + '''
    EXEC ' + QUOTENAME(@TargetDatabase)+'.dbo.sp_executesql @SQL'
EXEC (@CreateViewStatement);

虽然代码看起来有点多,但主要区别在于现在逻辑的核心在动态 SQL 之外(只需要针对任意数据库执行该逻辑)。假设单引号是我在这里唯一需要担心的转义机制,我认为这将使我能够构建逻辑来迭代所有视图,在需要时设置 DROP 命令等,而不必从偏移量中转义所有内容.当我有完整的解决方案时,我会 post 回来。


好的,这是我的完整解决方案:

CREATE PROCEDURE [dbo].[usp_Copy_View_To_Database]
    @ViewName SYSNAME, -- The name of the view to copy over
    @DatabaseName SYSNAME, -- The name of the database to copy the view to    
    @overwrite bit = 1 -- Whether to overwrite any existing view
AS
    IF DB_ID(@DatabaseName) IS NULL -- Validate the database name exists
    BEGIN
       RAISERROR('Invalid Destination Database Name passed',16,1)
       RETURN
    END    
    SET NOCOUNT ON
    IF @overwrite = 1 -- If set to overwrite, try to drop the remote view
    BEGIN    
        DECLARE @DropViewStatement NVARCHAR(MAX) =
            'EXEC ' + QUOTENAME(@DatabaseName) + '.sys.sp_executesql N''DROP VIEW IF EXISTS ' + QUOTENAME(@ViewName) + ';'';'
        EXEC (@DropViewStatement);
    END
    -- Extract the saved definition of the view
    DECLARE @ViewDefinition NVARCHAR(MAX);
    SELECT @ViewDefinition = definition FROM sys.sql_modules WHERE [object_id] = OBJECT_ID(@ViewName);
    -- Check for a mismatch between the internal view name and the expected name (TODO: Resolve this automatically?)
    IF @ViewDefinition NOT LIKE ('%' + @ViewName + '%')
    BEGIN
       DECLARE @InternalName NVARCHAR(MAX) = SUBSTRING(@ViewDefinition, 3, CHARINDEX(char(10), @ViewDefinition, 3)-4);
       PRINT ('Warning: The view named '+@ViewName+' has an internal definition name that is different ('+@InternalName+'). This may have been caused by renaming the view after it was created. You will have to drop and recreate it with the correct name.')
    END
    -- Substitute any hard-coded references to the current database with the destination database
    SET @ViewDefinition = REPLACE(@ViewDefinition, db_name(), @DatabaseName); 
    -- Generate the dynamic SQL that will create the view on the remote database
    DECLARE @CreateViewStatement NVARCHAR(MAX) =
        'EXEC ' + QUOTENAME(@DatabaseName) + '.sys.sp_executesql N''' + REPLACE(@ViewDefinition,'''','''''') + ''';'
    --PRINT '@CreateViewStatement: ' + @CreateViewStatement -- Can be used for debugging
    -- Execute the create statement
    EXEC (@CreateViewStatement);

Note: Some of the escape sequences break syntax highlighting here, it's not as bad as it looks in terms of dynamic SQL statements: I submitted a bug to highlight.js (currently used by Whosebug), but it turns out it's actually SO's fault for disabling certain languages (tsql in this case) - so perhaps upvote this meta.stackexchange post if that annoys you too :)

解决方案可以采用以下方式:

EXECUTE [dbo].[usp_Copy_View_To_Database] 'SampleView', 'SomeOtherDatabase'

我认为以上是存储过程的 'appropriate' 工作块 - 复制单个视图。可以通过重复调用这个存储过程来复制多个视图,但我不一定想在这里自动化,因为其他人可能有他们想要复制的子集,或者使复制顺序不重要的相互依赖关系。

请注意,代码块是对 的警告检查,其中 SQL 可以在幕后定义中保存重命名视图的错误名称。如果我偶然发现了一种优雅的处理方式,我可能会进一步更新它。