5

毎晩実行するストアド プロシージャがあります。リンク サーバーから一部のデータを取得し、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('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 Database 'Database' cannot be open because it is offline.

4

3 に答える 3

3

t-sql ステートメントの一部のみを条件付きで準備する方法はないと思います (少なくとも、あなたが尋ねた方法ではありません)。

元のクエリの根本的な問題は、リモート データベースがオフラインになることがあるということではなく、リモート データベースがオフラインのときにクエリ オプティマイザーが実行プランを作成できないことです。その意味で、オフライン データベースは事実上、構文エラーのようなものです。つまり、クエリ プランの作成を妨げる状態であるため、実行する機会が得られる前にすべてが失敗します。

理由EXECUTEは、それを呼び出すクエリの実行時まで、渡されたクエリのコンパイルを延期するためです。これは、2 つのクエリ プランが潜在的に存在することを意味します。利用可能であり、EXECUTEステートメントが実際に実行されるまで作成されない別のものです。

したがって、そのように考えると、EXECUTE(または代わりにsp_executesql)を使用することは、1つの可能な解決策であるため、回避策ではありません。これは、クエリを 2 つの個別の実行プランに分割するための単なるメカニズムです。

そのことを念頭に置いて、問題を解決するために必ずしも動的 SQL を使用する必要はありません。2 番目のストアド プロシージャを使用して、同じ結果を得ることができます。例えば:

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

もう 1 つの解決策は、リモート データベースが使用可能かどうかを確認することさえせずに、INSERT INTO _LOCALTABLEステートメントを実行してみて、失敗した場合はエラーを無視することです。ここでは少しおかしなことを言っていELSEますが、 forがない限りIF EXISTS、つまり、リモートデータベースがオフラインのときに別のことをしない限り、基本的にはエラーを抑制 (または無視) しているだけです。機能上の結果は、データがローカル テーブルにコピーされないという点で同じです。

次のように、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

要するに、元のクエリを個別のバッチに分割する必要があり、それを行う方法はたくさんあります。最適な解決策は、特定の環境と要件によって異なりますが、実際のクエリがこの質問で提示されたものと同じくらい簡単な場合は、元の回避策がおそらく適切な解決策です。

于 2013-09-17T22:46:48.273 に答える