T-SQL: Vòng qua một mảng các giá trị đã biết


89

Đây là kịch bản của tôi:

Giả sử tôi có một thủ tục được lưu trữ trong đó tôi cần gọi một thủ tục được lưu trữ khác trên một tập hợp các id cụ thể; Có cách nào để làm việc này không?

tức là thay vì cần làm điều này:

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

Làm điều gì đó như thế này:

*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

Mục tiêu chính của tôi ở đây chỉ đơn giản là khả năng bảo trì (dễ dàng xóa / thêm id khi doanh nghiệp thay đổi), có thể liệt kê tất cả Id trên một dòng duy nhất ... Hiệu suất không phải là vấn đề lớn


liên quan, nếu bạn cần phải lặp trên danh sách không số nguyên như varchars, giải pháp với con trỏ: lặp qua-một-list-of-dây-in-sql-server
Pac0

Câu trả lời:


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

Tôi đã hy vọng sẽ có một cách thanh lịch hơn, nhưng tôi nghĩ rằng điều này sẽ gần nhất có thể: Kết thúc bằng cách sử dụng kết hợp giữa sử dụng select / union ở đây và con trỏ từ ví dụ. Cảm ơn!
John

13
@ John: nếu bạn đang sử dụng 2008, bạn có thể làm một cái gì đó giống như GIÁ TRỊ INSERT @ids (4), (7), (12), (22), (19)
Peter Radocchia

2
Chỉ FYI, các bảng bộ nhớ như thế này thường nhanh hơn con trỏ (mặc dù đối với 5 giá trị, tôi khó có thể thấy điều đó tạo ra bất kỳ sự khác biệt nào), nhưng lý do lớn nhất tôi thích chúng là tôi tìm thấy cú pháp tương tự như những gì bạn tìm thấy trong mã ứng dụng , trong khi con trỏ xuất hiện (với tôi) tương đối khác.
Adam Robinson

mặc dù trên thực tế nó sẽ gây hại cho hiệu suất chỉ rất ít, tôi muốn chỉ ra rằng điều này lặp lại qua tất cả các số trong không gian xác định. giải pháp bên dưới với Trong khi tồn tại (Chọn * Từ @Ids) ... về mặt logic, âm thanh hơn (và thanh lịch hơn).
Der U

41

Những gì tôi làm trong trường hợp này là tạo một biến bảng để chứa các id.

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

- (hoặc gọi một hàm có giá trị bảng khác để tạo bảng này)

Sau đó lặp lại dựa trên các hàng trong bảng này

  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

hoặc là...

  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

Một trong hai cách tiếp cận trên nhanh hơn nhiều so với con trỏ (được khai báo dựa trên (các) Bảng Người dùng thông thường). Các biến giá trị trong bảng có đại diện không tốt vì khi được sử dụng không đúng cách, (đối với các bảng rất rộng với số lượng hàng lớn), chúng không hoạt động. Nhưng nếu bạn đang sử dụng chúng chỉ để giữ một giá trị khóa hoặc một số nguyên 4 byte, với một chỉ mục (như trong trường hợp này) thì chúng rất nhanh.


Cách tiếp cận trên tương đương hoặc chậm hơn so với con trỏ được khai báo trên một biến bảng. Nó chắc chắn không nhanh hơn. Tuy nhiên, nó sẽ nhanh hơn một con trỏ được khai báo với các tùy chọn mặc định trên các bảng người dùng thông thường.
Peter Radocchia

@ Peter, ahhh, có bạn là chính xác, tôi sai giả định rằng việc sử dụng một con trỏ hàm ý một bảng người dùng thường xuyên, không phải là một biến bảng .. Ihave chỉnh sửa để làm sáng tỏ sự khác biệt
Charles Bretana

16

sử dụng một biến con trỏ tĩnh và một hàm tách :

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

Con trỏ có một đại diện không tốt vì các tùy chọn mặc định khi được khai báo dựa trên bảng người dùng có thể tạo ra rất nhiều chi phí.

Nhưng trong trường hợp này, chi phí khá nhỏ, ít hơn bất kỳ phương pháp nào khác ở đây. STATIC yêu cầu SQL Server hiện thực hóa kết quả trong tempdb và sau đó lặp lại kết quả đó. Đối với những danh sách nhỏ như thế này, đó là giải pháp tối ưu.


7

Bạn có thể thử như sau:

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
Tôi sẽ thực hiện khai báo danh sách đó như thế này: @list ='4,7,12,22,19' + ','- vì vậy, điều đó hoàn toàn rõ ràng rằng danh sách phải kết thúc bằng dấu phẩy (nó không hoạt động nếu không có nó!).
AjV Jsy

5

Tôi thường sử dụng cách tiếp cận sau

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

Tạo kết nối với DB của bạn bằng ngôn ngữ lập trình thủ tục (ở đây là Python) và thực hiện vòng lặp ở đó. Bằng cách này, bạn cũng có thể thực hiện các vòng lặp phức tạp.

# 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))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.