終了する前に、ストアドプロシージャから部分的な結果(単純な選択として)を返す必要があります。
それは可能ですか?
はいの場合、それを行う方法は?
そうでない場合、回避策はありますか?
編集:私は手順のいくつかの部分を持っています。最初の部分では、いくつかの文字列を計算します。追加の操作を行うために、手順の後半でそれらを使用します。問題は、呼び出し元が文字列をできるだけ早く必要とすることです。そのため、その文字列を計算してそれを(どういうわけか、たとえばselectから)返し、それから作業を続ける必要があります。発信者は、貴重な文字列をはるかにすばやく取得します。
CallerはWebサービスです。
あなたはおそらくRAISERROR
オプション付きの NOWAIT
コマンドを探しています。
備考 :
RAISERRORは、PRINTの代わりに使用して、呼び出し元のアプリケーションにメッセージを返すことができます。
これはSELECT
ステートメントからの結果を返しませんが、メッセージ/文字列をクライアントに返すことができます。選択しているデータのクイックサブセットを返したい場合は、 FAST
クエリヒントを検討することをお勧めします。
クエリが最初のnumber_rowsの高速取得のために最適化されることを指定します。これは負でない整数です。最初のnumber_rowsが返された後、クエリは実行を継続し、完全な結果セットを生成します。
コメントに Shannon Severance によって追加されました:
From SQL Serverでのエラーとトランザクションの処理 Erland Sommarskogによる:
ただし、一部のAPIとツールはそれらの側でバッファリングする可能性があるため、
WITH NOWAIT
の影響を無効にすることに注意してください。
完全なコンテキストについては、ソースの記事を参照してください。
O.P.はすでに複数の結果セット(M.A.R.S.ではない)の送信を試みており、ストアドプロシージャが完了するのを待ってから、結果セットを返すことを確認しています。その状況を念頭に置いて、ここにいくつかのオプションがあります:
データが128バイトに収まるほど小さい場合、_SET CONTEXT_INFO
_を使用する可能性が高く、これにより_SELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;
_を介してその値が表示されます。ストアドプロシージャを_SELECT @@SPID;
_に対して実行する前にクイッククエリを実行し、_SqlCommand.ExecuteScalar
_を介してそれを取得する必要があるだけです。
私はこれをテストしただけで動作します。
@Davidのデータを「進行状況」テーブルに入れるという提案に似ていますが、クリーンアップや同時実行性、プロセス分離の問題を混乱させる必要はありません。
Guid
を作成し、パラメーターとしてストアドプロシージャに渡します。このGUIDは数回使用されるため、変数に格納します。CREATE TABLE ##MyProcess_{GuidFromApp};
_など)。テーブルには、必要なデータ型の列を含めることができます。データがある場合は、そのグローバル一時テーブルに挿入します。
アプリのコードで、データの読み取りを試みますが、SELECT
を_IF EXISTS
_でラップして、テーブルがまだ作成されていない場合でも失敗しないようにします。
_IF (OBJECT_ID('tempdb..[##MyProcess_{0}]')
IS NOT NULL)
BEGIN
SELECT * FROM [##MyProcess_{0}];
END;
_
String.Format()
を使用すると、_{0}
_をGuid変数の値に置き換えることができます。 _Reader.HasRows
_かどうかをテストし、trueの場合は結果を読み取ります。そうでない場合は、Thread.Sleep()
または何かを呼び出して、再度ポーリングします。
利点:
EXEC
/_sp_executesql
_呼び出し)
これをテストしましたが、期待どおりに動作します。次のコード例を使用して、自分で試してみることができます。
1つのクエリタブで、次を実行し、ブロックコメントの3行を強調表示して実行します。
_CREATE
--ALTER
PROCEDURE #GetSomeInfoBackQuickly
(
@MessageTableName NVARCHAR(50) -- might not always be a GUID
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX) = N'CREATE TABLE [##MyProcess_' + @MessageTableName
+ N'] (Message1 NVARCHAR(50), Message2 NVARCHAR(50), SomeNumber INT);';
-- Do some calculations
EXEC (@SQL);
SET @SQL = N'INSERT INTO [##MyProcess_' + @MessageTableName
+ N'] (Message1, Message2, SomeNumber) VALUES (@Msg1, @Msg2, @SomeNum);';
DECLARE @SomeNumber INT = CRYPT_GEN_RANDOM(2);
EXEC sp_executesql
@SQL,
N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
@Msg1 = N'wow',
@Msg2 = N'yadda yadda yadda',
@SomeNum = @SomeNumber;
WAITFOR DELAY '00:00:10.000';
SET @SomeNumber = CRYPT_GEN_RANDOM(3);
EXEC sp_executesql
@SQL,
N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
@Msg1 = N'wow',
@Msg2 = N'yadda yadda yadda',
@SomeNum = @SomeNumber;
WAITFOR DELAY '00:00:10.000';
GO
/*
DECLARE @TempTableID NVARCHAR(50) = NEWID();
RAISERROR('%s', 10, 1, @TempTableID) WITH NOWAIT;
EXEC #GetSomeInfoBackQuickly @TempTableID;
*/
_
[メッセージ]タブに移動し、GUID印刷されたものをコピーします。次に、別のクエリタブを開き、以下を実行して、GUIDからコピーした他のセッションの[メッセージ]タブで、1行目の変数の初期化に移動します。
_DECLARE @TempTableID NVARCHAR(50) = N'GUID-from-other-session';
EXEC (N'SELECT * FROM [##MyProcess_' + @TempTableID + N']');
_
叩き続ける F5。最初の10秒間は1つのエントリが表示され、次の10秒間は2つのエントリが表示されます。
SQLCLRを使用して、Webサービスまたはその他の方法でアプリにコールバックできます。
多分PRINT
/RAISERROR(..., 1, 10) WITH NOWAIT
を使用して文字列をすぐに返すことができますが、これは次の問題のために少しトリッキーになります:
VARCHAR(8000)
またはNVARCHAR(4000)
に制限されていますデフォルトでは、メッセージはプロセスが完了するまで送信されません。ただし、この動作は SqlConnection.FireInfoMessageEventOnUserErrors Property をtrue
に設定することで変更できます。ドキュメントは述べています:
FireInfoMessageEventOnUserErrorsをtrueに設定すると、以前は例外として扱われていたエラーがInfoMessageイベントとして処理されるようになりました。すべてのイベントはすぐに発生し、イベントハンドラーによって処理されます。 FireInfoMessageEventOnUserErrorsが
false
に設定されている場合、InfoMessageイベントは手順の最後に処理されます。
ここでの欠点は、ほとんどのSQLエラーでSqlException
が発生しなくなることです。この場合、メッセージイベントハンドラーに渡される追加のイベントプロパティをテストする必要があります。これは接続全体に当てはまるため、状況は少し複雑になりますが、管理不可能ではありません。
すべてのメッセージは同じレベルで表示され、個別に区別するための個別のフィールドやプロパティはありません。それらが受信される順序は、それらが送信される方法と同じである必要がありますが、それが十分に信頼できるかどうかはわかりません。あとで解析できるタグや何かを含める必要があるかもしれません。そうすれば、少なくともどれがどれであるかを確信することができます。
UPDATE:Strutzkyの回答( above )とコメントを参照してください。私が期待してここで説明するように動作しません。時間が許せば、理解を深めるためにさらに実験/読み進める必要があります...
呼び出し元がデータベースと非同期に対話するか、スレッド/マルチプロセスである場合、最初のセッションの実行中に2番目のセッションを開くことができるので、テーブルを作成して部分的なデータを保持し、手順の進行に応じてそれを更新できます。これは、トランザクション分離レベルの2番目のセッションで読み取ることができます。1 コミットされていない変更を読み取れるように設定します。
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table
1:srutzkyの回答のコメントとその後の更新に従って、監視対象のプロセスがトランザクションにラップされていない場合は分離レベルを設定する必要はありませんが、原因とならないような状況では習慣から外す傾向がありますこれらのケースで不要なときに害を及ぼす
もちろん、複数のプロセスがこのように動作している可能性がある場合(Webサーバーが同時ユーザーを受け入れ、それが当てはまらないことが非常にまれである場合)は、何らかの方法でこのプロセスの進行情報を特定する必要があります。 。おそらく、新しく作成したUUIDをキーとしてプロシージャに渡し、それを進行状況テーブルに追加して、次のように読み取ります。
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table WHERE process = <current_process_uuid>
この方法を使用して、SSMSで長時間実行される手動プロセスを監視しました。私はそれが生産でそれを使用することを検討するのにあまりにも「においがする」かどうかを決定することはできません...
ストアドプロシージャをバックグラウンドで(つまり非同期で)実行する必要がある場合は、Service Brokerを使用する必要があります。セットアップするのは少し面倒ですが、一度完了すると、ストアドプロシージャ(非ブロッキング)を開始し、進行状況メッセージを必要なだけ(または少しだけ)聞くことができます。