web-dev-qa-db-ja.com

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

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

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

186
MetaGuru

カーソルを使用する

補遺:[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更新ステートメントとして全体を実行できる場合は、最適です

226
Steven A. Lowe

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

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

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

データベースで作業する場合、ループの考え方を失おうとすると、実際のパフォーマンスが低下し、ロック/ブロッキングが発生して処理が遅くなります。どこでもループすると、システムのスケーラビリティが低下し、ユーザーが更新の遅延について不平を言うようになると、スピードアップが非常に難しくなります。

ループで呼び出すこのプロシージャのコンテンツを投稿すると、10回のうち9回賭けます。行のセットで動作するように記述できます。

53
KM.

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

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

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

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

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

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

ここで、udfMyFunctionは、ユーザーIDを取得し、ユーザーIDを使用して必要なことを行うユーザー作成関数です。

もう少し背景については http://www.sqlteam.com/article/user-defined-functions をご覧ください

カーソルは可能な限り避けるべきであることに同意します。そして、それは通常可能です!

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

6
randomsequence

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

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

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

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

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

idは重要です。

parentchildを適切なデータに置き換えます。関連する識別子または操作対象のデータセット全体。

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

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;
5
Erk

カーソルを使用せず、小さくて簡単なので、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;

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

2
Tom