在还原模式下目标数据库时防止错误(sql准备)

我有一个每晚运行的存储过程.

它从链接服务器中提取一些数据,并将其插入运行sql代理作业的服务器上的表中.在运行INSERT语句之前,该过程检查链接服务器上的数据库是否在线(STATE = 0).如果不是,则不运行INSERT语句.

IF EXISTS(
SELECT *
FROM OPENQUERY(_LINKEDSERVER,'
SELECT name, state FROM sys.databases
WHERE name = ''_DATABASENAME'' AND state = 0')
)
BEGIN
INSERT INTO _LOCALTABLE (A, B)
SELECT A, B FROM _LINKEDSERVER._DATABASENAME.dbo._REMOTETABLE
END

但是当远程数据库处于还原模式时,该过程会给出错误(无法完成延迟准备).这是因为在运行整个脚本之前会评估BEGIN和END之间的语句.当IF评估不正确时.由于_DATABASENAME处于还原模式,因此已经出错.

作为一种解决方法,我将INSERT语句放在execute函数中:

EXECUTE('INSERT INTO _LOCALTABLE (A, B) 
SELECT A, B FROM _LINKEDSERVER._DATABASENAME.dbo._REMOTETABLE')

但是在使用这部分sql之前,还有另一个更优雅的解决方案来阻止对此语句的评估吗?

我的方案涉及链接服务器.当然,同样的问题是数据库在同一台服务器上.

我希望有一些我还不知道的命令,这会阻止IF中的评估语法:

IF(Evaluation)
BEGIN
    PREPARE THIS PART ONLY IF Evaluation IS TRUE.
END

编辑答案:

我测试过:

IF(EXISTS
(
SELECT *
FROM sys.master_files F WHERE F.name = 'Database'
AND state = 0
))
BEGIN
    SELECT * FROM Database.dbo.Table
END
ELSE
BEGIN
    SELECT 'ErrorMessage'
END

哪个仍会生成此错误:
Msg 942,Level 14,State 4,Line 8
无法打开数据库“数据库”,因为它处于脱机状态.

最佳答案 我认为没有办法有条件地准备t-sql语句的一部分(至少不是你提出的方式).

原始查询的根本问题不是远程数据库有时是脱机的,而是当远程数据库脱机时,查询优化器无法创建执行计划.从这个意义上讲,脱机数据库实际上就像语法错误,即它是阻止创建查询计划的条件,因此整个事件在它有机会执行之前就失败了.

EXECUTE为您工作的原因是因为它延迟了传递给它的查询的编译直到调用它的查询的运行时,这意味着您现在可能有两个查询计划,一个用于主查询,检查是否远程查询db是可用的,而另一个不会被创建,除非并且直到实际执行EXECUTE语句.

因此,当您以这种方式考虑时,使用EXECUTE(或者sp_executesql)并不是一种解决方法,因为它是一种可能的解决方案.它只是一种将查询拆分为两个独立执行计划的机制.

考虑到这一点,您不一定要使用动态SQL来解决您的问题.您可以使用第二个存储过程来实现相同的结果.例如:

-- create this sp (when the remote db is online, of course)
CREATE PROCEDURE usp_CopyRemoteData 
AS
BEGIN
  INSERT INTO _LOCALTABLE (A, B)
  SELECT A, B FROM _LINKEDSERVER._DATABASENAME.dbo._REMOTETABLE;
END
GO

然后您的原始查询如下所示:

IF EXISTS(
  SELECT *
  FROM OPENQUERY(_LINKEDSERVER,'
  SELECT name, state FROM sys.databases
  WHERE name = ''_DATABASENAME'' AND state = 0')
  )
BEGIN
  exec usp_CopyRemoteData;
END

另一种解决方案是甚至不打扰检查远程数据库是否可用,只是尝试运行INSERT INTO _LOCALTABLE语句并在失败时忽略错误.我在这里有点滑稽,但除非您的IF EXISTS有一个ELSE,即除非您在远程数据库脱机时做了不同的事情,否则您基本上只是抑制(或忽略)错误.功能结果相同,因为没有数据被复制到本地表.

您可以使用try / catch在t-sql中执行此操作,如下所示:

BEGIN TRY
  /* Same definition for this sp as above. */
  exec usp_CopyRemoteData;

  /* You need the sp; this won't work:
  INSERT INTO _LOCALTABLE (A, B)
  SELECT A, B FROM _LINKEDSERVER._DATABASENAME.dbo._REMOTETABLE
  */
END TRY
BEGIN CATCH
  /* Do nothing, i.e. suppress the error. 
    Or do something different?
  */
END CATCH

公平地说,这将抑制sp引发的所有错误,而不仅仅是由远程数据库脱机引起的错误.并且您仍然具有与原始查询相同的根问题,并且需要存储过程或动态SQL来正确捕获有问题的错误. BOL就是一个非常好的例子;有关详细信息,请参阅本页的“不受TRY … CATCH构造影响的错误”部分:http://technet.microsoft.com/en-us/library/ms175976(v=sql.105).aspx

最重要的是,您需要将原始查询拆分为不同的批次,并且有很多方法可以做到这一点.最佳解决方案取决于您的特定环境和要求,但如果您的实际查询与此问题中提供的查询一样简单,那么您的原始解决方案可能是一个很好的解决方案.

点赞