T-SQL: Perulangan melalui larik nilai yang diketahui

90

Inilah skenario saya:

Katakanlah saya memiliki prosedur tersimpan di mana saya perlu memanggil prosedur tersimpan lainnya pada satu set id tertentu; apakah ada cara untuk melakukan ini?

yaitu, alih-alih perlu melakukan ini:

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

Melakukan sesuatu seperti ini:

*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

Tujuan utama saya di sini hanyalah kemudahan perawatan (mudah untuk menghapus / menambahkan id saat bisnis berubah), dapat mencantumkan semua Id dalam satu baris ... Kinerja seharusnya tidak menjadi masalah besar

John
sumber
terkait, jika Anda perlu mengulang pada daftar non-integer seperti varchars, solusi dengan cursor: iterate-through-a-list-of-strings-in-sql-server
Pac0

Jawaban:

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
Adam Robinson
sumber
Saya berharap akan ada cara yang lebih elegan, tetapi saya pikir ini akan sedekat yang saya bisa dapatkan: Akhirnya menggunakan hibrida antara menggunakan select / unions di sini, dan kursor dari contoh. Terima kasih!
Yohanes
13
@ John: jika Anda menggunakan 2008, Anda dapat melakukan sesuatu seperti INSERT @ids VALUES (4), (7), (12), (22), (19)
Peter Radocchia
2
Sekadar informasi, tabel memori seperti ini umumnya lebih cepat daripada kursor (meskipun untuk 5 nilai saya hampir tidak dapat melihat itu membuat perbedaan), tetapi alasan terbesar saya menyukainya adalah karena saya menemukan sintaksnya mirip dengan apa yang Anda temukan dalam kode aplikasi , sedangkan kursor tampak (bagi saya) relatif berbeda.
Adam Robinson
meskipun dalam praktiknya hanya akan merugikan kinerja sedikit, saya ingin menunjukkan bahwa ini berulang melalui semua angka dalam ruang yang ditentukan. solusi di bawah ini dengan While There (Pilih * Dari @Ids) ... secara logis lebih sehat (dan lebih elegan).
Der U
41

Apa yang saya lakukan dalam skenario ini adalah membuat variabel tabel untuk menampung Id.

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

- (atau panggil fungsi nilai tabel lain untuk menghasilkan tabel ini)

Kemudian putar berdasarkan baris dalam tabel ini

  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

atau...

  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

Salah satu dari pendekatan di atas jauh lebih cepat daripada kursor (dideklarasikan terhadap Tabel Pengguna biasa). Variabel nilai tabel memiliki reputasi yang buruk karena ketika digunakan secara tidak benar, (untuk tabel yang sangat lebar dengan jumlah baris yang besar) mereka tidak berkinerja baik. Tetapi jika Anda menggunakannya hanya untuk menyimpan nilai kunci atau integer 4 byte, dengan indeks (seperti dalam kasus ini) mereka sangat cepat.

Charles Bretana
sumber
Pendekatan di atas sama dengan atau lebih lambat dari kursor yang dideklarasikan pada variabel tabel. Ini tentu tidak lebih cepat. Ini akan lebih cepat daripada kursor yang dideklarasikan dengan opsi default pada tabel pengguna biasa.
Peter Radocchia
@ Peter, ahhh, ya Anda benar, saya salah berasumsi bahwa menggunakan kursor menyiratkan tabel pengguna biasa, bukan variabel tabel .. Saya telah mengedit untuk memperjelas perbedaannya
Charles Bretana
16

gunakan variabel kursor statis dan fungsi split :

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

Kursor memiliki reputasi yang buruk karena opsi default saat dideklarasikan terhadap tabel pengguna dapat menghasilkan banyak overhead.

Tetapi dalam hal ini biaya overhead cukup minimal, kurang dari metode lain di sini. STATIC memberitahu SQL Server untuk mewujudkan hasil dalam tempdb dan kemudian mengulanginya. Untuk daftar kecil seperti ini, ini adalah solusi optimal.

Peter Radocchia
sumber
7

Anda dapat mencoba seperti di bawah ini:

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
Ramakrishna Talla
sumber
6
Saya akan membuat pernyataan daftar itu seperti ini: @list ='4,7,12,22,19' + ','- jadi sangat jelas bahwa daftar harus diakhiri dengan koma (tidak akan bekerja tanpanya!).
AjV Jsy
5

Saya biasanya menggunakan pendekatan berikut

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
kristof
sumber
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
Moshe
sumber
0

Buat koneksi ke DB Anda menggunakan bahasa pemrograman prosedural (di sini Python), dan lakukan perulangan di sana. Dengan cara ini Anda juga dapat melakukan loop rumit.

# 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))
LoMaPh
sumber