Wie*_*314 5 sql prepared-statement sql-server-2008
我有一个每晚运行的存储过程。它从链接服务器中提取一些数据并将其插入到运行 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
Run Code Online (Sandbox Code Playgroud)
但是,当远程数据库处于还原模式时,该过程会出错(无法完成延迟准备)。这是因为在运行整个脚本之前评估 BEGIN 和 END 之间的语句。IF 评估不正确时也是如此。并且因为 _DATABASENAME 处于恢复模式,这已经给出了错误。
作为一种解决方法,我将 INSERT 语句放在执行函数中:
EXECUTE('INSERT INTO _LOCALTABLE (A, B)
SELECT A, B FROM _LINKEDSERVER._DATABASENAME.dbo._REMOTETABLE')
Run Code Online (Sandbox Code Playgroud)
但是在使用这部分 sql 之前,是否有另一种更优雅的解决方案来防止对该语句的评估?
我的方案涉及链接服务器。当然,同样的问题是当数据库在同一台服务器上时。
我希望有一些我还不知道的命令,这会阻止 IF 中的评估语法:
IF(Evaluation)
BEGIN
PREPARE THIS PART ONLY IF Evaluation IS TRUE.
END
Run Code Online (Sandbox Code Playgroud)
编辑关于答案:
我测试过:
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
Run Code Online (Sandbox Code Playgroud)
仍然会生成此错误:消息 942,级别 14,状态 4,第 8 行数据库“数据库”无法打开,因为它处于脱机状态。
我认为没有一种方法可以有条件地只准备 t-sql 语句的一部分(至少不是按照您询问的方式)。
\n\n原始查询的根本问题不是远程数据库有时会脱机,而是查询优化器在远程数据库脱机时无法创建执行计划。从这个意义上说,脱机数据库实际上就像一个语法错误,即它是一个阻止创建查询计划的条件,因此整个事情在有机会执行之前就失败了。
\n\n对您有用的原因EXECUTE是因为它将传递给它的查询的编译推迟到调用它的查询的运行时,这意味着您现在可能有两个查询计划,一个用于主查询,用于检查远程数据库是否可用,而另一个则不会创建,除非该EXECUTE语句实际执行。
因此,当您以这种方式思考时,使用EXECUTE(或者,sp_executesql)与其说是一种解决方法,不如说是一种可能的解决方案。它只是一种将查询拆分为两个单独的执行计划的机制。
考虑到这一点,您不一定必须使用动态 SQL 来解决您的问题。您可以使用第二个存储过程来获得相同的结果。例如:
\n\n-- create this sp (when the remote db is online, of course)\nCREATE PROCEDURE usp_CopyRemoteData \nAS\nBEGIN\n INSERT INTO _LOCALTABLE (A, B)\n SELECT A, B FROM _LINKEDSERVER._DATABASENAME.dbo._REMOTETABLE;\nEND\nGO\nRun Code Online (Sandbox Code Playgroud)\n\n那么您的原始查询如下所示:
\n\nIF EXISTS(\n SELECT *\n FROM OPENQUERY(_LINKEDSERVER,\'\n SELECT name, state FROM sys.databases\n WHERE name = \'\'_DATABASENAME\'\' AND state = 0\')\n )\nBEGIN\n exec usp_CopyRemoteData;\nEND\nRun Code Online (Sandbox Code Playgroud)\n\n另一种解决方案是甚至不去检查远程数据库是否可用,只需尝试运行该INSERT INTO _LOCALTABLE语句并在失败时忽略错误。我在这里有点开玩笑,但是除非ELSE你有一个IF EXISTS,即除非你在远程数据库离线时做一些不同的事情,否则你基本上只是抑制(或忽略)错误。功能结果是相同的,没有数据被复制到本地表。
您可以在 t-sql 中使用 try/catch 来做到这一点,如下所示:
\n\nBEGIN TRY\n /* Same definition for this sp as above. */\n exec usp_CopyRemoteData;\n\n /* You need the sp; this won\'t work:\n INSERT INTO _LOCALTABLE (A, B)\n SELECT A, B FROM _LINKEDSERVER._DATABASENAME.dbo._REMOTETABLE\n */\nEND TRY\nBEGIN CATCH\n /* Do nothing, i.e. suppress the error. \n Or do something different?\n */\nEND CATCH\nRun Code Online (Sandbox Code Playgroud)\n\n公平地说,这将抑制 sp 引发的所有错误,而不仅仅是由远程数据库脱机引起的错误。而且您仍然遇到与原始查询相同的根本问题,并且需要存储过程或动态 SQL 来正确捕获有问题的错误。BOL 有一个很好的例子;有关详细信息,请参阅本页的“不受 TRY\xe2\x80\xa6CATCH 构造影响的错误”部分:http://technet.microsoft.com/en-us/library/ms175976 (v=sql.105).aspx
\n\n最重要的是,您需要将原始查询拆分为单独的批次,并且有很多方法可以做到这一点。最佳解决方案取决于您的具体环境和要求,但如果您的实际查询与此问题中提出的查询一样简单,那么您最初的解决方法可能是一个很好的解决方案。
\n