web-dev-qa-db-ja.com

エラー:「INSERT EXECステートメントはネストできません。」 「INSERT-EXECステートメント内でROLLBACKステートメントを使用できません。」これを解決する方法は?

Sp1Sp2Sp3の3つのストアドプロシージャがあります。

1つ目(Sp1)は2つ目(Sp2)を実行し、返されたデータを@tempTB1に保存し、2つ目は3つ目(Sp3)を実行して保存します@tempTB2へのデータ。

Sp2を実行すると動作し、Sp3からすべてのデータが返されますが、問題はSp1にあります。実行すると、次のエラーが表示されます。

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

execute Sp2の場所を変更しようとすると、別のエラーが表示されます。

INSERT-EXECステートメント内でROLLBACKステートメントを使用することはできません。

85
HAJJAJ

これは、ストアドプロシージャのチェーンからデータを「バブル」しようとするときの一般的な問題です。 SQL Serverの制限は、一度にアクティブにできるINSERT-EXECは1つだけです。 ストアドプロシージャ間でデータを共有する方法 を参照することをお勧めします。これは、このタイプの問題を回避するためのパターンに関する非常に徹底的な記事です。

たとえば、回避策は、Sp3をテーブル値関数に変えることです。

87
eddiegroves

これは、いくつかの巨大な複雑な作成関数または実行されたsql文字列呼び出しを使用せずにSQL Serverでこれを行うための唯一の「簡単な」方法であり、どちらもひどい解決策です。

  1. 一時テーブルを作成する
  2. ストアドプロシージャデータをopenrowsetします

例:

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を追加することはできません。そのため、テーブル変数ではなく一時テーブルを使用する必要があります。ほとんどの場合、一時テーブルを実行するので、より良いはずです。

17
Mitch Stokely

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

*/
9
Matt Luckham

この問題に対する私の回避策は、単一のハッシュ一時テーブルが呼び出されたprocのスコープ内にあるという原則を常に使用することでした。そのため、procパラメーターにオプションスイッチがあります(既定ではオフに設定されています)。これをオンにすると、呼び出されたprocは、呼び出したprocで作成された一時テーブルに結果を挿入します。過去にさらに一歩進んで、呼び出されたprocにいくつかのコードを入れて、スコープ内に単一のハッシュテーブルが存在するかどうかを確認し、そうであればコードを挿入し、そうでなければ結果セットを返します。うまく動作しているようです-proc間で大きなデータセットを渡す最良の方法。

8
Matt Luckham

回避策は、prodsの1つをテーブル値関数に変換することです。私はそれが常に可能であるとは限らず、独自の制限を導入していることを認識しています。ただし、少なくとも1つの手順がこれに適した候補であることが常にわかりました。ソリューションに「ハッキング」を導入しないため、このソリューションが気に入っています。

4
Roman K

このトリックは私に役立ちます。

リモートサーバーでは、最後の挿入コマンドは前のコマンドの結果が実行されるのを待つため、リモートサーバーではこの問題は発生しません。同じサーバーではそうではありません。

回避策のためにその状況に利益をもたらします。

リンクサーバーを作成する適切なアクセス許可がある場合は、それを実行します。リンクサーバーと同じサーバーを作成します。

  • sSMSで、サーバーにログインします
  • 「サーバーオブジェクト
  • [リンクサーバー]を右クリックし、次に[新しいリンクサーバー]をクリックします。
  • ダイアログで、リンクサーバーの名前を指定します。例:THISSERVER
  • サーバータイプは「その他のデータソース」です
  • プロバイダー:Microsoft OLE SQL Server用DBプロバイダー
  • データソース:IP、それはローカルホストなので、ドット(。)でもかまいません
  • [セキュリティ]タブに移動し、3番目の[ログインの現在のセキュリティコンテキストを使用して作成する]を選択します
  • 必要に応じて、サーバーオプション(3番目のタブ)を編集できます。
  • [OK]を押すと、リンクサーバーが作成されます

これで、SP1のSqlコマンドは

insert into @myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2

私を信じて、それはあなたがSP2で動的挿入を持っていても動作します

3
ainasiart

出力を静的テーブルに保存するだけではどうですか?好む

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

2つ以上のsprocでコードが重複するという同じ問題と懸念がありました。最終的に「モード」の属性を追加しました。これにより、1つのsproc内に共通のコードが存在し、sprocのモード指定フローと結果セットが許可されました。

1
phoenixAZ

出力カーソル変数を内部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をフェッチする準備が整います。ループしてから閉じて、割り当てを解除します。

0