次のスクリプトを実行すると、正常に実行されます。
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プロシージャを使用すると、ソースコードは以下のリンクにあります。
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)
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;'
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
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;'
_