【发布时间】:2018-01-31 22:40:04
【问题描述】:
来自自学成才的数据仓库人员的第一篇文章。我做了大量的搜索和阅读以了解我现在的位置,但无法超越这个症结所在。
背景:作为我们夜间 ETL 工作的一部分,我们必须将许多表从许多远程 DB(链接服务器)复制到暂存区 DB。表副本完成后,我继续将暂存区 DB 转换为生产表。
由于远程数据库都具有相同的架构,我在生产数据库中创建了一个存储过程来完成这项工作。存储过程接受远程数据库名称和表名称的参数。在夜间作业中,SQL Server Agent 运行一个 SSIS 包;该包包含每个远程数据库的一个(重试循环)SSIS 任务;所有任务同时运行;每个任务使用一个变量将数据库名称传递给 SQL 文件;然后 SQL 文件为每个表调用一次存储过程。
远程表和本地暂存区表示例:
远程:[FLTA].[cstone].[csdbo].[CLIENT]
本地:[FLTAL].dbo.[FLTA CLIENT]
存储过程非常简单,删除旧表并使用 SELECT 从远程数据库创建新副本。大概是这样的:
CREATE PROCEDURE dbo.spTableCopyNew
(@p VARCHAR(50), @Tablename VARCHAR(50))
AS
-- Drop the existing table
EXEC('IF OBJECT_ID(''[' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +']'', ''U'') IS NOT NULL
DROP TABLE [' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +']'
)
-- Copy the new table
EXEC('SELECT * into [' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +']
FROM [' + @p + '].[cstone].[csdbo].[' + @Tablename +']'
)
GO
SQL 大致如下:
-- Set local variables for the remote server connection, the local database name, and the table prefix
DECLARE @Prefix varchar(50)
-- Accept the variables passed in from the SSIS task
SET @Prefix = ?
-- Copy the two tables
EXEC Datawarehouse.dbo.spTableCopy @Prefix, 'CLIENT'
EXEC Datawarehouse.dbo.spTableCopy @Prefix, 'PATIENT'
维护轻而易举:当我们需要从所有远程数据库中获取新表时,我只需将其添加到“productionLoad.sql”文件中即可。
效果真的很好......除非它不起作用。
由于未知的原因,有时无法复制表。而且由于我在复制新表之前删除了现有表,因此有时这会进一步破坏事情的发展。我的 SSIS 任务将在每个远程 DB 上重试多达 3 次,因此偶尔的失败没什么大不了的。但是如果同一个远程数据库在一个晚上出现三个故障,我会很糟糕的。
我当前的解决方案尝试是将远程表复制到临时表,然后仅在复制成功后,删除本地表并将临时表重命名为“真实”表。这让我想到了这个问题:
当从存储过程调用时,我无法让sp_rename 工作,以重命名存在于与存储过程不同的数据库中的表。我创建了新变量来解析表达式,然后将这些变量发送到sp_rename,因为我无法将表达式传递到该存储过程中。
这是我对新存储过程的尝试:
CREATE PROCEDURE dbo.spTableCopy
(@p VARCHAR(50), @Tablename VARCHAR(50))
AS
BEGIN
EXEC('USE [' + @p + 'L]')
-- Create variables for schema and table names
-- Since sp_rename can accept variables, but not expresssions containing variables.
DECLARE @RemoteTable VARCHAR(50) = '[' + @p + '].[cstone].[csdbo].[' + @Tablename +']'
DECLARE @LocalTableTemp VARCHAR(50) = '[' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +'_temp]'
DECLARE @LocalTable VARCHAR(50) = '' + @p + ' ' + @Tablename + ''
-- Check for previous temp table and drop it
EXEC('IF OBJECT_ID(''[' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +'_temp]'', ''U'') IS NOT NULL
DROP TABLE [' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +'_temp]'
)
-- Copy the new table
EXEC('SELECT * into ' + @LocalTableTemp + '
FROM ' + @RemoteTable + ''
)
-- Drop the existing table
EXEC('IF OBJECT_ID(''[' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +']'', ''U'') IS NOT NULL
DROP TABLE [' + @p + 'L].dbo.[' + @p + ' ' + @Tablename +']'
)
-- Rename temp table to real table
EXEC sp_rename @LocalTableTemp, @LocalTable
END
GO
当它作为普通 SQL 代码执行时,这一切都有效,但是当我将它变成存储过程时,sp_rename 失败(其他一切都有效)。最终表格 [FLTAL CLIENT_temp] 在那里,包含正确的数据。
sp_rename 返回以下错误:
消息 290,级别 16,状态 2,过程 sp_rename,第 318 行
使用对象“Object”、方法“LockMatchID”的 EXECUTE 语句无效。
我已经用这种方式战斗了太久了。
我只是搞砸了语法吗?
我可以让sp_rename 使用“USE”在其他数据库上工作吗?
如果没有,如果我在每个暂存区域数据库中复制我的sp_tableCopy 是否有效?
如果不是,即使我同时多次调用此存储过程,catch-try 是否会在此存储过程中工作?
我还能做些什么来从失败的表副本中恢复?
我尚未采用的替代解决方案:成功创建临时表后,TRUNC 现有表并将临时表中的所有内容插入到真实表中。不过这看起来很乱。
附:我们的 IT 人员正在“调查”复制失败的性质。
【问题讨论】:
-
Can I get sp_rename to work on other DBs with "USE?"不是您当前的方式(因为在EXEC('USE...')部分之后,您只需返回默认数据库。EXEC 部分仅适用于括号),但你可以做类似exec somedb.dbo.sp_rename的东西。 -
如果您将 spTableCopy 放入目标数据库,您的生活会轻松很多。
-
关于您在这里进行的大量转义,
QUOTENAME和FORMATMESSAGE会让您(以及您未来的维护者)的生活更加愉快。 SQL Server 2012 之前的版本,在此类字符串上使用REPLACE仍然比串联更容易阅读。
标签: sql-server tsql stored-procedures