T-SQL: วนลูปผ่านอาร์เรย์ของค่าที่รู้จัก


90

นี่คือสถานการณ์ของฉัน:

สมมติว่าฉันมีกระบวนงานที่เก็บไว้ซึ่งฉันจำเป็นต้องเรียกใช้กระบวนงานอื่นที่เก็บไว้ในชุดของรหัสเฉพาะ มีวิธีทำไหม

กล่าวคือแทนที่จะต้องทำสิ่งนี้:

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 ทั้งหมดในบรรทัดเดียว ... ประสิทธิภาพไม่ควรเป็นปัญหาใหญ่


ที่เกี่ยวข้องหากคุณต้องการวนซ้ำในรายการที่ไม่ใช่จำนวนเต็มเช่น varchars วิธีแก้ปัญหาด้วยเคอร์เซอร์: วนซ้ำผ่าน a-list-of-strings-in-sql-server
Pac0

คำตอบ:


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

ฉันหวังว่าจะมีวิธีที่หรูหรากว่านี้ แต่ฉันคิดว่านี่จะใกล้เคียงที่สุดเท่าที่จะทำได้: จบลงด้วยการใช้ลูกผสมระหว่างการใช้ select / unions ที่นี่และเคอร์เซอร์จากตัวอย่าง ขอบคุณ!
John

13
@john: ถ้าคุณใช้ปี 2008 คุณสามารถทำบางอย่างเช่น INSERT @ids VALUES (4), (7), (12), (22), (19)
Peter Radocchia

2
เพียงแค่ FYI ตารางหน่วยความจำเช่นนี้โดยทั่วไปจะเร็วกว่าเคอร์เซอร์ (แม้ว่าสำหรับ 5 ค่าฉันแทบจะไม่เห็นว่าสร้างความแตกต่าง) แต่เหตุผลที่ใหญ่ที่สุดที่ฉันชอบคือฉันพบว่าไวยากรณ์ที่คล้ายกับที่คุณพบในรหัสแอปพลิเคชัน ในขณะที่เคอร์เซอร์ปรากฏ (สำหรับฉัน) จะค่อนข้างแตกต่างกัน
Adam Robinson

แม้ว่าในทางปฏิบัติจะเป็นอันตรายต่อประสิทธิภาพเพียงเล็กน้อยเท่านั้นฉันต้องการชี้ให้เห็นว่าสิ่งนี้วนซ้ำผ่านตัวเลขทั้งหมดภายในช่องว่างที่กำหนด วิธีแก้ปัญหาด้านล่างด้วย While มีอยู่ (เลือก * จาก @Ids) ... มีเหตุผลมากกว่า (และสง่างามมากขึ้น)
Der U

41

สิ่งที่ฉันทำในสถานการณ์นี้คือสร้างตัวแปรตารางเพื่อเก็บรหัส

  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

@ ปีเตอร์อ่าใช่คุณพูดถูกฉันคิดไม่ถูกว่าการใช้เคอร์เซอร์หมายถึงตารางผู้ใช้ทั่วไปไม่ใช่ตัวแปรตาราง .. ฉันแก้ไขเพื่อให้ชัดเจนถึงความแตกต่าง
Charles Bretana

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

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) และทำวนซ้ำที่นั่น วิธีนี้คุณสามารถทำลูปที่ซับซ้อนได้เช่นกัน

# 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.