クエリによって返された行ごとにストアドプロシージャを1回実行するにはどうすればよいですか?


206

ユーザーデータを特定の方法で変更するストアドプロシージャがあります。user_idを渡します。テーブルでクエリを実行したいのですが、user_idごとに、そのuser_idでストアドプロシージャを1回実行します。

これに対するクエリをどのように記述しますか?


5
どのRDBMSを指定する必要があります
-SQL

5
おそらくストアドプロシージャはまったく必要ありません。ストアドプロシージャの「機能」について正確に説明していただけますか?おそらく、プロセス全体を単一の更新ステートメントとして表現できます。「レコードごとに1回実行する」パターンは、可能であれば、通常は避ける必要があります。
トマラック2009年

どのデータベースを使用していますか?
SOユーザー

1
あなたはこの記事を読むべきです...項目2はカーソルを使用しないでくださいcodeproject.com/KB/database/sqldodont.aspx...mind私も時期尚早な最適化に反対しています。
Michael Prewecki 2009年

7
@MichaelPrewecki:その書き下ろしの記事をさらに読むと、項目10が「何をしているのかわからない限り、サーバー側カーソルを使用しないでください」であることがわかります。これは「自分のやっていることを知っている」の場合だと思います。
ゲイブ2013年

回答:


246

カーソルを使用する

補遺:[MS SQLカーソルの例]

declare @field1 int
declare @field2 int
declare cur CURSOR LOCAL for
    select field1, field2 from sometable where someotherfield is null

open cur

fetch next from cur into @field1, @field2

while @@FETCH_STATUS = 0 BEGIN

    --execute your sproc on each row
    exec uspYourSproc @field1, @field2

    fetch next from cur into @field1, @field2
END

close cur
deallocate cur

MS SQLでは、ここに例の記事があります

カーソルはセットベースの操作よりも低速ですが、手動のwhileループよりは高速です。このSO質問の詳細

補遺2:数個以上のレコードを処理する場合は、まずそれらを一時テーブルにプルし、一時テーブルにカーソルを合わせます。これにより、SQLがテーブルロックにエスカレートするのを防ぎ、操作を高速化します

補遺3:そしてもちろん、ストアドプロシージャが各ユーザーIDに対して実行していることをすべてインライン化し、すべてを単一のSQL更新ステートメントとして実行できる場合、それが最適です。


21
宣言の後に「オープンカーソル」を見逃しました-これにより、「カーソルが開いていません」エラーが発生しました。編集を行う担当者がいません。
フィオナ-myaccessible.website 2010年

5
コメントに投票することで、人々に感謝することができます。誰が知っているのか、おそらくその方法で、次回は編集を担当する担当者がいるでしょう!:-)
ロビノ2014年

ストアドプロシージャで使用されるフィールドのJOINS句とWHERE句のインデックスを確認してください。適切なインデックスを追加した後、ループでSPの呼び出しを大幅に高速化しました。
マシュー

1
長時間の実行によって引き起こされる潜在的なロックの問題を回避するために一時テーブルを使用することを思い出していただきありがとうございます。
トニー

場合によっては、ストアドプロシージャが大きすぎたり、複雑すぎたりして、バグが発生するリスクを負わずにインライン化できないことがあります。パフォーマンスが最終的な優先事項ではない場合、カーソルループでSPを実行することが多くの場合、最も実用的な選択です。
Suncat2000

55

ループする必要がある場合は、メソッドを変更してみてください!

親ストアドプロシージャ内で、処理する必要のあるデータを含む#tempテーブルを作成します。子ストアドプロシージャを呼び出すと、#tempテーブルが表示され、カーソルやループなしでデータセット全体を処理できるようにテーブルを処理できます。

これは実際には、この子のストアドプロシージャが何をしているかによって異なります。UPDATEを実行している場合は、#tempテーブルに「から更新」して結合し、ループなしで1つのステートメントですべての作業を実行できます。INSERTとDELETEについても同じことができます。IFを使用して複数の更新を行う必要がある場合UPDATE FROMは、#tempテーブルを使用して複数に更新し、CASEステートメントまたはWHERE条件を使用できます。

データベースで作業しているときにループの考え方を失おうとすると、それは実際のパフォーマンスの低下であり、ロック/ブロックを引き起こし、処理を遅くします。どこでもループすると、システムのスケーリングがうまくいかなくなり、ユーザーがリフレッシュが遅いと不満を感じ始めたときにスピードアップが非常に難しくなります。

ループで呼び出したいこのプロシージャのコンテンツを投稿してください。10回のうち9回は賭けるので、一連の行を処理するように記述できます。


3
子のsprocを制御していると仮定すると、非常に良い回避策として+1
スティーブンA.ロウ

少し考えてみると、この溶液ははるかに優れています!
encc

7
セットベースの操作が常に望ましいです。ただし、SPの変更は常にオプションとは限らないことに注意してください。ベンダーが提供するソリューションを検討してください。一部のユーザーは可視性すらなく、カーソルまたはループオプションのみを残している場合があります。私のショップでは、開発者はすべてを見ることができますが、トリガー、ネストされたプロシージャ、操作されているレコードの数などのために、ソリューションがベンダーのアプリケーションの外部で構築されている場合、解決する多くのハードルがあります。アプリケーションの複雑さは、単にレコードをカーソルで移動することです。
Steve Mangiameli 2017

11

この置換のようなものがテーブルとフィールド名に必要になります。

Declare @TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare @i Int, @MaxI Int, @UserID nVarchar(50)

Insert into @TableUser
Select User_ID
From Users 
Where (My Criteria)
Select @MaxI = @@RowCount, @i = 1

While @i <= @MaxI
Begin
Select @UserID = UserID from @TableUsers Where MyRowCount = @i
Exec prMyStoredProc @UserID
Select

 @i = @i + 1, @UserID = null
End

2
whileループはカーソルよりも遅い
スティーブンA.ロウ

DeclareカーソルSQL構文またはステートメントはサポートされていません(??)
MetaGuru 2009年

9

動的クエリでそれを行うことができます。

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);

6

これは、ストアドプロシージャが実行していることをすべて複製するユーザー定義関数では実行できませんか?

SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition

ここで、udfMyFunctionは、ユーザーIDを取り込み、それを使用して必要なことをすべて実行するために作成する関数です。

http://www.sqlteam.com/article/user-defined-functionsを参照してください

カーソルは可能な限り避けなければならないことに同意します。そしてそれは通常可能です!

(もちろん、私の答えは、SPからの出力の取得のみに関心があり、実際のデータを変更しないことを前提としています。「特定の方法でユーザーデータを変更する」ことは、元の質問から少しあいまいです。だから私はこれを可能な解決策として提供すると思いました。実際にはあなたが何をしているのかに依存します!)


1
OP: "ユーザーデータを特定の方法で変更するストアドプロシージャ" MSDN:ユーザー定義関数を使用して、データベースの状態を変更するアクションを実行することはできません。ただし、SQLSVR 2014には問題がないようです
ジョニー

6

テーブル変数または一時テーブルを使用します。

前述したように、カーソルは最後の手段です。多くのリソースを使用し、ロックを発行し、SQLを適切に使用する方法を理解していないことを示している可能性があります。

補足:テーブルの行を更新するためにカーソルを使用するソリューションに出くわしました。精査した結果、すべてを1つのUPDATEコマンドで置き換えることができることがわかりました。ただし、この場合、ストアドプロシージャを実行する必要がある場合、単一のSQLコマンドは機能しません。

次のようなテーブル変数を作成します(大量のデータを処理している場合やメモリが不足している場合は、代わりに一時テーブルを使用してください)。

DECLARE @menus AS TABLE (
    id INT IDENTITY(1,1),
    parent NVARCHAR(128),
    child NVARCHAR(128));

id重要です。

交換parentしてchildいくつかの良いデータ、例えば、関連する識別子または上で動作するデータのセット全体で。

テーブルにデータを挿入します。例:

INSERT INTO @menus (parent, child) 
  VALUES ('Some name',  'Child name');
...
INSERT INTO @menus (parent,child) 
  VALUES ('Some other name', 'Some other child name');

いくつかの変数を宣言します。

DECLARE @id INT = 1;
DECLARE @parentName NVARCHAR(128);
DECLARE @childName NVARCHAR(128);

最後に、テーブル内のデータに対してwhileループを作成します。

WHILE @id IS NOT NULL
BEGIN
    SELECT @parentName = parent,
           @childName = child 
        FROM @menus WHERE id = @id;

    EXEC myProcedure @parent=@parentName, @child=@childName;

    SELECT @id = MIN(id) FROM @menus WHERE id > @id;
END

最初の選択は、一時テーブルからデータをフェッチします。2番目の選択は@idを更新します。MIN行が選択されていない場合はnullを返します。

別の方法は、テーブルに行がある間にループしSELECT TOP 1、選択した行を一時テーブルから削除することです。

WHILE EXISTS(SELECT 1 FROM @menuIDs) 
BEGIN
    SELECT TOP 1 @menuID = menuID FROM @menuIDs;

    EXEC myProcedure @menuID=@menuID;

    DELETE FROM @menuIDs WHERE menuID = @menuID;
END;

3

カーソルを使用せず、小さくて簡単なDave Rinconの動的クエリ方法が好きです。デイブ、共有してくれてありがとう。

しかし、Azure SQLでの必要性のために、クエリに "明確な"ものがあるため、次のようにコードを変更する必要がありました。

Declare @SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines 
SELECT @SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' + 
              convert(varchar(8),tenantid) + ';' 
              from Dim_Machine
              where iscurrent = 1
              FOR XML PATH(''))

--for debugging print the sql 
print @SQL;

--execute the generated sql script
exec sp_executesql @SQL;

これが誰かを助けることを願っています...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.