Sp1
、Sp2
、Sp3
の3つのストアドプロシージャがあります。
1つ目(Sp1
)は2つ目(Sp2
)を実行し、返されたデータを@tempTB1
に保存し、2つ目は3つ目(Sp3
)を実行して保存します@tempTB2
へのデータ。
Sp2
を実行すると動作し、Sp3
からすべてのデータが返されますが、問題はSp1
にあります。実行すると、次のエラーが表示されます。
INSERT EXECステートメントはネストできません
execute Sp2
の場所を変更しようとすると、別のエラーが表示されます。
INSERT-EXECステートメント内でROLLBACKステートメントを使用することはできません。
これは、ストアドプロシージャのチェーンからデータを「バブル」しようとするときの一般的な問題です。 SQL Serverの制限は、一度にアクティブにできるINSERT-EXECは1つだけです。 ストアドプロシージャ間でデータを共有する方法 を参照することをお勧めします。これは、このタイプの問題を回避するためのパターンに関する非常に徹底的な記事です。
たとえば、回避策は、Sp3をテーブル値関数に変えることです。
これは、いくつかの巨大な複雑な作成関数または実行されたsql文字列呼び出しを使用せずにSQL Serverでこれを行うための唯一の「簡単な」方法であり、どちらもひどい解決策です。
例:
INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')
注: 'set fmtonly off'を使用する必要があります。また、ストアドプロシージャパラメーターを含む文字列またはテーブル名のいずれかに、openrowset呼び出し内でこれに動的SQLを追加することはできません。そのため、テーブル変数ではなく一時テーブルを使用する必要があります。ほとんどの場合、一時テーブルを実行するので、より良いはずです。
OK、jimharkに勧められているのは、古いシングルハッシュテーブルアプローチの例です。
CREATE PROCEDURE SP3 as
BEGIN
SELECT 1, 'Data1'
UNION ALL
SELECT 2, 'Data2'
END
go
CREATE PROCEDURE SP2 as
BEGIN
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
INSERT INTO #tmp1
EXEC SP3
else
EXEC SP3
END
go
CREATE PROCEDURE SP1 as
BEGIN
EXEC SP2
END
GO
/*
--I want some data back from SP3
-- Just run the SP1
EXEC SP1
*/
/*
--I want some data back from SP3 into a table to do something useful
--Try run this - get an error - can't nest Execs
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
DROP TABLE #tmp1
CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))
INSERT INTO #tmp1
EXEC SP1
*/
/*
--I want some data back from SP3 into a table to do something useful
--However, if we run this single hash temp table it is in scope anyway so
--no need for the exec insert
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
DROP TABLE #tmp1
CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))
EXEC SP1
SELECT * FROM #tmp1
*/
この問題に対する私の回避策は、単一のハッシュ一時テーブルが呼び出されたprocのスコープ内にあるという原則を常に使用することでした。そのため、procパラメーターにオプションスイッチがあります(既定ではオフに設定されています)。これをオンにすると、呼び出されたprocは、呼び出したprocで作成された一時テーブルに結果を挿入します。過去にさらに一歩進んで、呼び出されたprocにいくつかのコードを入れて、スコープ内に単一のハッシュテーブルが存在するかどうかを確認し、そうであればコードを挿入し、そうでなければ結果セットを返します。うまく動作しているようです-proc間で大きなデータセットを渡す最良の方法。
回避策は、prodsの1つをテーブル値関数に変換することです。私はそれが常に可能であるとは限らず、独自の制限を導入していることを認識しています。ただし、少なくとも1つの手順がこれに適した候補であることが常にわかりました。ソリューションに「ハッキング」を導入しないため、このソリューションが気に入っています。
このトリックは私に役立ちます。
リモートサーバーでは、最後の挿入コマンドは前のコマンドの結果が実行されるのを待つため、リモートサーバーではこの問題は発生しません。同じサーバーではそうではありません。
回避策のためにその状況に利益をもたらします。
リンクサーバーを作成する適切なアクセス許可がある場合は、それを実行します。リンクサーバーと同じサーバーを作成します。
これで、SP1のSqlコマンドは
insert into @myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2
私を信じて、それはあなたがSP2で動的挿入を持っていても動作します
出力を静的テーブルに保存するだけではどうですか?好む
-- SubProcedure: subProcedureName
---------------------------------
-- Save the value
DELETE lastValue_subProcedureName
INSERT INTO lastValue_subProcedureName (Value)
SELECT @Value
-- Return the value
SELECT @Value
-- Procedure
--------------------------------------------
-- get last value of subProcedureName
SELECT Value FROM lastValue_subProcedureName
理想的ではありませんが、非常にシンプルであり、すべてを書き換える必要はありません。
UPDATE:以前のソリューションは並列クエリ(非同期およびマルチユーザーアクセス)ではうまく機能しないため、現在Iamは一時テーブルを使用しています
-- A local temporary table created in a stored procedure is dropped automatically when the stored procedure is finished.
-- The table can be referenced by any nested stored procedures executed by the stored procedure that created the table.
-- The table cannot be referenced by the process that called the stored procedure that created the table.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NULL
CREATE TABLE #lastValue_spGetData (Value INT)
-- trigger stored procedure with special silent parameter
EXEC dbo.spGetData 1 --silent mode parameter
ネストされたspGetData
ストアドプロシージャのコンテンツ
-- Save the output if temporary table exists.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NOT NULL
BEGIN
DELETE #lastValue_spGetData
INSERT INTO #lastValue_spGetData(Value)
SELECT Col1 FROM dbo.Table1
END
-- stored procedure return
IF @silentMode = 0
SELECT Col1 FROM dbo.Table1
2つ以上のsprocでコードが重複するという同じ問題と懸念がありました。最終的に「モード」の属性を追加しました。これにより、1つのsproc内に共通のコードが存在し、sprocのモード指定フローと結果セットが許可されました。
出力カーソル変数を内部spに宣言します。
@c CURSOR VARYING OUTPUT
次に、カーソルcを返したい選択に宣言します。次に、カーソルを開きます。次に、参照を設定します。
DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
SELECT ...
OPEN c
SET @c = c
閉じたり、再割り当てしたりしないでください。
次に、外部spから内部spを呼び出して、次のようなカーソルパラメータを指定します。
exec sp_abc a,b,c,, @cOUT OUTPUT
内側のspが実行されると、@cOUT
をフェッチする準備が整います。ループしてから閉じて、割り当てを解除します。