T-SQL:既知の値の配列をループする


89

これが私のシナリオです:

特定のIDのセットで別のストアドプロシージャを呼び出す必要があるストアドプロシージャがあるとします。これを行う方法はありますか?

つまり、これを行う必要がある代わりに:

exec p_MyInnerProcedure 4
exec p_MyInnerProcedure 7
exec p_MyInnerProcedure 12
exec p_MyInnerProcedure 22
exec p_MyInnerProcedure 19

このようなことをする:

*magic where I specify my list contains 4,7,12,22,19*

DECLARE my_cursor CURSOR FAST_FORWARD FOR
*magic select*

OPEN my_cursor 
FETCH NEXT FROM my_cursor INTO @MyId
WHILE @@FETCH_STATUS = 0
BEGIN

exec p_MyInnerProcedure @MyId

FETCH NEXT FROM my_cursor INTO @MyId
END

ここでの私の主な目標は、単純に保守性(ビジネスの変化に応じてIDを簡単に削除/追加できる)であり、すべてのIDを1行で一覧表示できるようにすることです...パフォーマンスはそれほど大きな問題ではありません。


関連して、varcharsのような非整数リストで反復する必要がある場合は、カーソルを使用して解決します:iterate-through-a-list-of-strings-in-sql-server
Pac0 2017

回答:


105
declare @ids table(idx int identity(1,1), id int)

insert into @ids (id)
    select 4 union
    select 7 union
    select 12 union
    select 22 union
    select 19

declare @i int
declare @cnt int

select @i = min(idx) - 1, @cnt = max(idx) from @ids

while @i < @cnt
begin
     select @i = @i + 1

     declare @id = select id from @ids where idx = @i

     exec p_MyInnerProcedure @id
end

もっとエレガントな方法があることを望んでいましたが、これは私が得ることができる限り近いと思います。ここでのselect / unionsの使用と、例のカーソルのハイブリッドを使用することになりました。ありがとう!
ジョン

13
@john:2008を使用している場合は、INSERT @ids VALUES(4)、(7)、(12)、(22)、(19)のようなことができます
Peter Radocchia 2009年

2
参考までに、このようなメモリテーブルは一般にカーソルよりも高速です(ただし、5つの値の場合、違いが生じることはほとんどわかりません)が、私が気に入っている最大の理由は、アプリケーションコードにあるものと同様の構文を見つけることです。 、一方、カーソルは(私には)比較的異なっているように見えます。
アダムロビンソン

実際にはパフォーマンスに悪影響を与えることはほとんどありませんが、これが定義されたスペース内のすべての数値を反復することを指摘したいと思います。以下のWhileexists(Select * From @Ids)を使用したソリューションは、論理的により健全です(そしてよりエレガントです)。
Der U

41

このシナリオで私が行うことは、IDを保持するためのテーブル変数を作成することです。

  Declare @Ids Table (id integer primary Key not null)
  Insert @Ids(id) values (4),(7),(12),(22),(19)

-(または別のテーブル値関数を呼び出してこのテーブルを生成します)

次に、このテーブルの行に基づいてループします

  Declare @Id Integer
  While exists (Select * From @Ids)
    Begin
      Select @Id = Min(id) from @Ids
      exec p_MyInnerProcedure @Id 
      Delete from @Ids Where id = @Id
    End

または...

  Declare @Id Integer = 0 -- assuming all Ids are > 0
  While exists (Select * From @Ids
                where id > @Id)
    Begin
      Select @Id = Min(id) 
      from @Ids Where id > @Id
      exec p_MyInnerProcedure @Id 
    End

上記のアプローチはいずれも、カーソルよりもはるかに高速です(通常のユーザーテーブルに対して宣言されています)。テーブル値の変数は、不適切に使用すると(行数が多い非常に幅の広いテーブルの場合)パフォーマンスが低下するため、レピュテーションが悪くなります。ただし、キー値または4バイト整数を保持するためだけにそれらを使用している場合(この場合のように)、それらは非常に高速です。


上記のアプローチは、テーブル変数で宣言されたカーソルと同等か、それよりも低速です。それは確かに速くはありません。ただし、通常のユーザーテーブルでデフォルトオプションを使用して宣言されたカーソルよりも高速です。
Peter Radocchia 2009年

@Peter、ああ、そうです、あなたは正しいです。カーソルの使用は、テーブル変数ではなく、通常のユーザーテーブルを意味すると誤って想定しています。区別を明確にするために編集しました
Charles Bretana 2009年

16

静的カーソル変数と分割関数を使用します。

declare @comma_delimited_list varchar(4000)
set @comma_delimited_list = '4,7,12,22,19'

declare @cursor cursor
set @cursor = cursor static for 
  select convert(int, Value) as Id from dbo.Split(@comma_delimited_list) a

declare @id int
open @cursor
while 1=1 begin
  fetch next from @cursor into @id
  if @@fetch_status <> 0 break
  ....do something....
end
-- not strictly necessary w/ cursor variables since they will go out of scope like a normal var
close @cursor
deallocate @cursor

ユーザーテーブルに対して宣言されたときのデフォルトオプションは多くのオーバーヘッドを生成する可能性があるため、カーソルのレピュテーションは不適切です。

ただし、この場合、オーバーヘッドはごくわずかであり、他のどの方法よりも少なくなります。STATICは、SQL Serverに、結果をtempdbでマテリアライズしてから、それを反復処理するように指示します。このような小さなリストの場合、これが最適なソリューションです。


7

あなたは以下のように試すことができます:

declare @list varchar(MAX), @i int
select @i=0, @list ='4,7,12,22,19,'

while( @i < LEN(@list))
begin
    declare @item varchar(MAX)
    SELECT  @item = SUBSTRING(@list,  @i,CHARINDEX(',',@list,@i)-@i)
    select @item

     --do your stuff here with @item 
     exec p_MyInnerProcedure @item 

    set @i = CHARINDEX(',',@list,@i)+1
    if(@i = 0) set @i = LEN(@list) 
end

6
私はそのリスト宣言を次のようにします:@list ='4,7,12,22,19' + ','-リストがコンマで終わる必要があることは完全に明らかです(それなしでは機能しません!)。
AjV Jsy 2014

5

私は通常、次のアプローチを使用します

DECLARE @calls TABLE (
    id INT IDENTITY(1,1)
    ,parameter INT
    )

INSERT INTO @calls
select parameter from some_table where some_condition -- here you populate your parameters

declare @i int
declare @n int
declare @myId int
select @i = min(id), @n = max(id) from @calls
while @i <= @n
begin
    select 
        @myId = parameter
    from 
        @calls
    where id = @i

        EXECUTE p_MyInnerProcedure @myId
    set @i = @i+1
end

2
CREATE TABLE #ListOfIDs (IDValue INT)

DECLARE @IDs VARCHAR(50), @ID VARCHAR(5)
SET @IDs = @OriginalListOfIDs + ','

WHILE LEN(@IDs) > 1
BEGIN
SET @ID = SUBSTRING(@IDs, 0, CHARINDEX(',', @IDs));
INSERT INTO #ListOfIDs (IDValue) VALUES(@ID);
SET @IDs = REPLACE(',' + @IDs, ',' + @ID + ',', '')
END

SELECT * 
FROM #ListOfIDs

0

手続き型プログラミング言語(ここではPython)を使用してDBに接続し、そこでループを実行します。このようにして、複雑なループも実行できます。

# make a connection to your db
import pyodbc
conn = pyodbc.connect('''
                        Driver={ODBC Driver 13 for SQL Server};
                        Server=serverName;
                        Database=DBname;
                        UID=userName;
                        PWD=password;
                      ''')
cursor = conn.cursor()

# run sql code
for id in [4, 7, 12, 22, 19]:
  cursor.execute('''
    exec p_MyInnerProcedure {}
  '''.format(id))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.