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


98

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

最初の(Sp1)は2番目の()を実行してSp2返されたデータを保存@tempTB1し、2番目は3番目の(Sp3)を実行してデータを保存します@tempTB2

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

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

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

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

回答:


99

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

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


1
リンク切れまたは応答しないサイト。
SouravA 2018年

6
技術的な理由がそれを許可しない理由は何ですか?これに関する情報は見つかりません。
jtate

残念ながら、これは多くの場合オプションではありません。多くの種類の重要な情報は、システムストアドプロシージャからのみ確実に利用できます(特定のケースでは、それぞれの管理ビューに信頼できない/古いデータが含まれているためです。例として、から返される情報がありますsp_help_jobactivity)。
GSerg

21

これは、巨大な複雑な作成関数や実行されたSQL文字列呼び出しなしにSQL Serverでこれを行う唯一の「簡単な」方法です。どちらもひどい解決策です。

  1. 一時テーブルを作成する
  2. ストアドプロシージャデータをそれにオープンセットする

例:

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


SET FMTONLY OFFを使用する必要はありません。IF(1 = 0)を追加するだけで、プロシージャが通常返すのと同じデータ型の空のテーブルを返すことができます。
ギジェルモ・グティエレス2013

1
一時テーブルとテーブル変数では、データの格納方法が異なります。クエリオプティマイザーはテーブル変数の統計を維持しないため、テーブル変数は小さな結果セットに使用されることになっています。したがって、大規模なデータセットの場合、ほとんどの場合、一時テーブルを使用する方が適切です。mssqltips.com/sqlservertip/2825/…
gh9 '25

@ gh9はい、しかし、これはとにかく大きな結果セットの恐ろしい考えです。統計と一時データベースの実際のテーブルの使用は、大きなオーバーヘッドを引き起こす可能性があります。現在の値が1行のレコードセット(複数のテーブルをクエリする)を返すプロシージャと、それをテーブル変数に格納し、それを同じ形式の別のテーブルの値と比較するプロシージャがあります。一時テーブルからテーブル変数に変更すると、平均時間が8ミリ秒から2ミリ秒に短縮されました。これは、1秒間に1秒間に数回呼び出され、夜間のプロセスで100,000回呼び出される場合に重要です。
Jason Goemaat 14

なぜ統計をテーブル変数で維持したいのですか?重要なのは、クエリが終了した後に破棄される一時テーブルをRAMに作成することです。定義上、このようなテーブルで作成された統計は使用されません。一般に、テーブル変数のデータは可能な限りRAMに残るため、データがSQL Serverで使用可能なRAMの量よりも少ないシナリオでは、データが一時テーブルよりも高速になります(現在、SQLの100 GB以上のメモリプールの場合)サーバーはほとんど常に)
Geoff Griswald

ただし、これは拡張ストアドプロシージャでは機能しません。エラーは次のとおりです。プロシージャ...のステートメント 'EXECUTE <procedurename> @retval OUTPUT'が拡張ストアドプロシージャを呼び出すため、メタデータを特定できませんでした
GSerg

11

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

*/

この回避策も使用しました。アイデアをありがとう!
SQL_Guy

素晴らしい回避策。これにより、一時テーブルのスコープについて詳しく知ることができました。たとえば、一時テーブルが外部で宣言されている場合、dynsql文字列で一時テーブルを使用できることに気づきませんでした。ここで同様の概念。どうもありがとう。
jbd

9

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


1
私はこの答えが好きです。例を挙げれば、もっと多くの票を獲得できると思います。
ジムハルク2016年

私はこれを何年もやっています。SQL Azureでもまだ必要ですか?
Nick Allan

6

このトリックは私にはうまくいきます。

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

回避策としてその状況を利用してください。

リンクサーバーを作成するための適切な権限がある場合は、それを行います。リンクサーバーと同じサーバーを作成します。

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

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

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

信じてください、SP2で動的に挿入しても機能します


4

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


しかし、デメリットは、関数が複雑な場合の例外処理の問題です。
Muflix 2017

2

ストアドプロシージャの結果を一時テーブルにインポートしようとしたときにこの問題が発生し、そのストアドプロシージャが独自の操作の一部として一時テーブルに挿入されました。SQL Serverでは、同じプロセスが2つの異なる一時テーブルに同時に書き込むことができないという問題があります。

受け入れられたOPENROWSETの回答は問題なく機能しますが、プロセスで動的SQLまたは外部OLEプロバイダーを使用しないようにする必要があるため、別の方法を使用しました。

私が見つけた1つの簡単な回避策は、ストアドプロシージャの一時テーブルをテーブル変数に変更することでした。一時テーブルの場合とまったく同じように機能しますが、他の一時テーブルの挿入と競合しなくなりました。

コメントを控えるために、私はあなたの何人かが書こうとしていることを知っています、パフォーマンスキラーとしてテーブル変数を警告しています...私があなたに言えることはすべて、2020年にそれが配当を支払うことでテーブル変数を恐れないということです。これが2008年で、私のデータベースが16GB RAMのサーバーでホストされ、5400RPM HDDで実行されている場合、私はあなたに同意するかもしれません。しかし、それは2020年であり、プライマリストレージとしてSSDアレイと数百 GBのRAM を持っています。会社のデータベース全体をテーブル変数にロードできますが、それでも十分なRAMがあります。

テーブル変数がメニューに戻ってきました!


1

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


1

静的テーブルに出力を保存するだけではどうですか?お気に入り

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

理想的ではありませんが、とてもシンプルで、すべてを書き直す必要はありません。

更新:以前のソリューションは並列クエリ(非同期およびマルチユーザーアクセス)ではうまく機能しないため、一時テーブルを使用するようになりました

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

一般に、テーブルのようにSProcアドホックを作成することはできません。このアプローチはあまり知られていないか受け入れられていないため、参照を増やして例を拡張する必要があります。また、ANSI-SQLではラムダ式アプローチを許可しないSProc実行よりもラムダ式に似ています。
GoldBishop 2017

動作しますが、並列クエリ(非同期およびマルチユーザーアクセス)でもうまく動作しないことがわかりました。したがって、今は一時テーブルアプローチを使用しています。回答を更新しました。
Muflix 2017

1
Tempテーブルのロジックは適切です。私が心配していたのはSProcリファレンスでした。Sprocは本質的に直接クエリすることはできません。テーブル値関数は、直接クエリできます。更新されたロジックで言及したように、最善のアプローチは一時テーブル、セッション、インスタンス、またはグローバルであり、その時点から動作します。
GoldBishop 2017年

0

出力カーソル変数を内側の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

C#などの他の関連テクノロジーを使用できる場合は、組み込みのSQLコマンドをTransactionパラメーターと共に使用することをお勧めします。

var sqlCommand = new SqlCommand(commandText, null, transaction);

私はここで見つけることができるこの機能を実証するシンプルなコンソールアプリを作成しました:https : //github.com/hecked12/SQL-Transaction-Using-C-Sharp

つまり、C#を使用すると、この制限を克服して、各ストアドプロシージャの出力を検査し、その出力を好きなように使用できます。たとえば、別のストアドプロシージャにフィードを送ることができます。出力に問題がなければ、トランザクションをコミットできます。それ以外の場合は、ロールバックを使用して変更を元に戻すことができます。


-1

SQL Server 2008 R2では、テーブルの列に不一致があり、ロールバックエラーが発生しました。ストアドプロシージャから返されたものと一致するように、insert-execステートメントによって入力されたsqlcmdテーブル変数を修正したところ、問題は解決しました。org_codeがありませんでした。Windows cmdファイルでは、ストアドプロシージャの結果をロードして選択します。

set SQLTXT= declare @resets as table (org_id nvarchar(9), org_code char(4), ^
tin(char9), old_strt_dt char(10), strt_dt char(10)); ^
insert @resets exec rsp_reset; ^
select * from @resets;

sqlcmd -U user -P pass -d database -S server -Q "%SQLTXT%" -o "OrgReport.txt"

OPは、ネストされたストアドプロシージャでinsert-execステートメントを使用するときに発生するエラーについて尋ねていました。問題は、「INSERTステートメントの選択リストに含まれる項目が挿入リストよりも少ないです。SELECT値の数はINSERT列の数と一致する必要があります」などの別のエラーを返します。
Losbear

これは、このメッセージを誤って取得する可能性があるという警告です。
user3448451 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.