web-dev-qa-db-ja.com

この状況で「INSERT EXECステートメントをネストできない」例外を回避する方法

次のスクリプトを実行すると、正常に実行されます。

declare @temp table
(
    name varchar(255),
    field varchar(255),
    filename varchar(255),
    filegroup varchar(255),
    size varchar(255),
    maxsize varchar(255),
    growth varchar(255),
    usage varchar(255)
);
INSERT @temp
   exec sp_msforeachdb  @command1='use ?; Exec sp_helpfile;' 

しかし、自分のsp_foreachdbプロシージャを使用すると、ソースコードは以下のリンクにあります。

より信頼性が高く、より柔軟なsp_MSforeachdb

declare @temp table
(
    name varchar(255),
    field varchar(255),
    filename varchar(255),
    filegroup varchar(255),
    size varchar(255),
    maxsize varchar(255),
    growth varchar(255),
    usage varchar(255)
);
INSERT @temp
   exec sp_foreachdb  @command='use ?; Exec sp_helpfile;'   

例外が発生します(その手順に例外処理を追加したことに注意してください)

--EXCEPTION WAS CAUGHT--
THE ERROR NUMBER:8164

SEVERITY: 16
STATE: 1

PROCEDURE: sp_foreachdb
LINE NUMBER: 165

ERROR MESSAGE: 
An INSERT EXEC statement cannot be nested.
------------------------------------ the sql ------------------------------------

SELECT name FROM sys.databases WHERE 1=1 AND state_desc = N'ONLINE' AND is_read_only = 0
Msg 16916, Level 16, State 1, Procedure sp_foreachdb, Line 239
A cursor with the name 'c' does not exist.
Msg 16916, Level 16, State 1, Procedure sp_foreachdb, Line 240
A cursor with the name 'c' does not exist.

(0 row(s) affected)
2

Aaronのsp_foreachdbのソースコードには、次の行が含まれています。

INSERT #x EXEC sp_executesql @sql;

あなたのエラーメッセージによると:

INSERT EXECステートメントはネストできません。

したがって、以下のようなコードはINSERT xxx EXEC xxxコードをネストしているため無効になります。

INSERT @temp
exec sp_msforeachdb  @command1='use ?; Exec sp_helpfile;'
2
James Anderson

Sp_foreachdbが単一の結果セットを返す場合は、分散クエリメソッドを使用して独自のサーバーに接続するとうまくいきます。

create table #temp 
(
    name varchar(255),
    field varchar(255),
    filename varchar(255),
    filegroup varchar(255),
    size varchar(255),
    maxsize varchar(255),
    growth varchar(255),
    usage varchar(255)
);

insert into #temp
select  *
FROM OPENROWSET('SQLNCLI', 'SERVER=****;UID=****;PWD=****',
' exec sp_foreachdb  @command=''  Exec ?..sp_helpfile;''  WITH RESULT SETS         ((name varchar(255),
field varchar(255),
filename varchar(255),
filegroup varchar(255),
size varchar(255),
maxsize varchar(255),
growth varchar(255),
usage varchar(255))); ')

select * from #temp

上記のクエリは、sp_foreachdb実行の最初の結果セットのみを返します。ただし、以下のクエリは、すべてのデータベースの結果を単一の結果セットで返します。

create table #temp 
(
    name varchar(255),
    field varchar(255),
    filename varchar(255),
    filegroup varchar(255),
    size varchar(255),
    maxsize varchar(255),
    growth varchar(255),
    usage varchar(255)
);

exec sp_foreachdb  @command='INSERT INTO #temp  Exec ?..sp_helpfile;'
select * from #temp
1
SelmanAY

Aaronの動的部分を取り除くことにより、AaronのSPを適応させることができます。動的部分は、指定された引数に基づいて_sys.databases_からデータベース名のみを読み取るクエリを構築することになっています。動的SQLは、クエリを最も効率的にし、かつ保守可能にするために選択されます。特定のニーズを考慮すると、いくつかの犠牲が必要になる場合があります。

ただし、_sys.databases_システムビューには通常非常に多くの行がないため、以下で提供する書き換えによってパフォーマンスが大幅に低下することはないと主張しますが、いずれにしてもOPTION (RECOMPILE) 最後に。どんなに遅いとしても、私は約束することができますが、かなり醜くなるかもしれません。

書き換え方法は以下の通りです。アーロンの手順は、パラメーター値がチェックされる繰り返しパターンを使用してクエリを構築し、その結果に基づいて、次のように追加のクエリが動的クエリに追加されます。

_SET @sql = N'SELECT name FROM sys.databases WHERE 1=1'
+ CASE WHEN some_condition1 THEN 'AND some_filter1' ELSE '' END
+ CASE WHEN some_condition2 THEN 'AND some_filter2' ELSE '' END
+ ...
_

これを書き換える方法を次に示します。

_SELECT name FROM sys.databases WHERE 1=1
AND (some_filter1 OR opposite_of_some_condition1)
AND (some_filter2 OR opposite_of_some_condition2)
AND ...
OPTION (RECOMPILE);
_

たとえば、_@system_only_は、database_id IN (1,2,3,4)フィルターを含めるかどうかを次のように制御します。

_SET @sql = N'SELECT name FROM sys.databases WHERE 1=1'
    + CASE WHEN @system_only = 1 THEN
        ' AND database_id IN (1,2,3,4)'
        ELSE '' END
_

書き換えられたクエリは、次のようにパラメータ化されます。

_SELECT name FROM sys.databases WHERE 1=1
  AND (database_id IN (1,2,3,4) OR @system_only <> 1)  -- or: @system_only = 0
_

結果のクエリをカーソルの代わりに直接使用する

_ SELECT CASE WHEN @suppress_quotename = 1 THEN
            db
        ELSE
            QUOTENAME(db)
        END
   FROM #x ORDER BY db
_

ご覧のとおり、最後のタッチはSELECT部分​​に移動する必要があります。ここで、新しいクエリの単純な_SELECT name_が次のように置き換えられます。

_ SELECT CASE WHEN @suppress_quotename = 1 THEN name ELSE QUOTENAME(name) END
_

もちろん、SPで不要になった_#x_テーブルを削除することもできます。

最後の注意点は、各データベースに対して実行する予定の特定のスクリプトに関するものです。の代わりに

_@command1='use ?; Exec sp_helpfile;'
_

あなたはちょうど持つことができます

_@command1='Exec ?..sp_helpfile;'
_
1
Andriy M