T-SQL: Bilinen değerler dizisi arasında döngü yapmak


90

İşte benim senaryom:

Diyelim ki, belirli bir kimlik kümesinde başka bir saklı yordamı çağırmam gereken bir saklı yordamım var; bunu yapmanın bir yolu var mı?

yani, bunu yapmaya ihtiyaç duymak yerine:

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

Bunun gibi bir şey yapmak:

*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

Buradaki Ana hedefim basitçe sürdürülebilirlik (işletme değiştikçe kimliklerin çıkarılması / eklenmesi kolay), tüm kimlikleri tek bir satırda listeleyebilmek ... Performans bir sorun kadar büyük olmamalı


ilgili, varchars gibi tamsayı olmayan bir listede yineleme yapmanız gerekiyorsa, imleçli çözüm: sql-server'da dizelerin listesini yineleme
Pac0

Yanıtlar:


106
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

Daha zarif bir yol olacağını umuyordum, ancak bunun olabildiğince yakın olacağını düşünüyorum: Buradaki seç / birlikleri kullanmak ile örnekteki imleci kullanmak arasında bir melez kullanarak sona erdi. Teşekkürler!
John

13
@john: 2008 kullanıyorsanız, INSERT @ids VALUES (4), (7), (12), (22), (19)
Peter Radocchia

2
Bilginize, bunun gibi bellek tabloları genellikle imleçlerden daha hızlıdır (5 değer için bunun fark yarattığını pek göremiyorum), ancak onları sevmemin en büyük nedeni sözdizimini uygulama kodunda bulduğunuza benzer bulmamdır. oysa imleçler (bana) görece farklı görünmektedir.
Adam Robinson

pratikte performansa çok az zarar verecek olsa da, bunun tanımlanan alandaki tüm sayılarda yinelendiğini belirtmek isterim. While ile aşağıdaki çözüm (Select * From @Ids) ... mantıksal olarak daha fazla ses (ve daha zarif).
Der U

41

Bu senaryoda yaptığım şey, kimlikleri tutmak için bir tablo değişkeni oluşturmaktır.

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

- (veya bu tabloyu oluşturmak için başka bir tablo değerli işlevi çağırın)

Ardından bu tablodaki satırlara göre döngü yapın

  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

veya...

  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

Yukarıdaki yaklaşımlardan herhangi biri bir imleçten çok daha hızlıdır (normal Kullanıcı Tablolarına göre bildirilir). Tablo değerli değişkenlerin kötü bir temsilcisi vardır çünkü yanlış kullanıldıklarında (çok sayıda satır içeren çok geniş tablolar için) performans göstermezler. Ancak, bunları yalnızca bir anahtar değerini veya 4 baytlık bir tamsayıyı tutmak için kullanıyorsanız (bu durumda olduğu gibi) son derece hızlıdırlar.


Yukarıdaki yaklaşım, bir tablo değişkeninde bildirilen bir imleç ile eşdeğerdir veya ondan daha yavaştır. Kesinlikle daha hızlı değil. Yine de, normal kullanıcı tablolarında bir imlecin w / default seçenekleriyle bildirilmesinden daha hızlı olurdu.
Peter Radocchia

@Peter, ahhh, evet haklısınız, yanlış bir şekilde imleç kullanmanın bir tablo değişkeni değil, normal bir kullanıcı tablosu anlamına geldiğini varsayıyorum ..
Ayrımı

16

statik bir imleç değişkeni ve bir bölme işlevi kullanın :

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

Kullanıcı tablolarına göre bildirildiğinde varsayılan seçenekler çok fazla ek yük oluşturabileceğinden imleçlerin kötü bir temsilcisi vardır.

Ancak bu durumda ek yük oldukça azdır, buradaki diğer yöntemlerden daha azdır. STATIC, SQL Server'a sonuçları tempdb'de gerçekleştirmesini ve ardından bunun üzerinde yinelemesini söyler. Bunun gibi küçük listeler için en uygun çözüm budur.


7

Aşağıdaki gibi deneyebilirsiniz:

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
Bu liste bildirimini şöyle yapardım: @list ='4,7,12,22,19' + ','- yani listenin virgülle bitmesi gerektiği tamamen açık (onsuz çalışmaz!).
AjV Jsy

5

Genellikle aşağıdaki yaklaşımı kullanırım

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

Prosedürel bir programlama dili (burada Python) kullanarak DB'nize bağlantı kurun ve burada döngü yapın. Bu şekilde karmaşık döngüler de yapabilirsiniz.

# 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))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.