T-SQL:循环遍历一组已知值

Posted

技术标签:

【中文标题】T-SQL:循环遍历一组已知值【英文标题】:T-SQL: Looping through an array of known values 【发布时间】:2009-10-19 15:04:12 【问题描述】:

这是我的场景:

假设我有一个存储过程,我需要在一组特定的 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...性能不应该是一个大问题

【问题讨论】:

相关,如果你需要迭代像varchars这样的非整数列表,用光标解决:iterate-through-a-list-of-strings-in-sql-server 【参考方案1】:
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:如果您使用的是 2008,您可以执行类似 INSERT @ids VALUES (4), (7), (12), (22), (19) 之类的操作 仅供参考,像这样的内存表通常比游标快(尽管对于 5 个值,我几乎看不出有什么区别),但我喜欢它们的最大原因是我发现语法类似于你会在应用程序代码中找到什么,而光标(在我看来)相对不同。 虽然它在实践中只会损害性能,但我想指出的是,它会遍历定义空间内的所有数字。下面的解决方案 While exists (Select * From @Ids)... 在逻辑上更合理(更优雅)。【参考方案2】:

在这种情况下我要做的是创建一个表变量来保存 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,啊,是的,你是对的,我错误地认为使用游标意味着常规用户表,而不是表变量。我已经编辑以明确区别【参考方案3】:

使用静态游标变量和split function:

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 中具体化结果,然后对其进行迭代。对于这样的小列表,这是最佳解决方案。

【讨论】:

【参考方案4】:

你可以尝试如下:

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

【讨论】:

我会像这样声明列表:@list ='4,7,12,22,19' + ',' - 所以很明显,列表必须以逗号结尾(没有它就行不通!)。 除了我们有两个选择之外,我看不出这个答案有任何缺陷【参考方案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

【讨论】:

【参考方案6】:
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

【讨论】:

【参考方案7】:

使用过程编程语言(此处为 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))

【讨论】:

在查看了所有这些 TSQL 示例之后,我刚刚得出了这个结论,当我看到你的答案时,我正在前往 PyCharm 的路上。

以上是关于T-SQL:循环遍历一组已知值的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL for 循环遍历列名并插入

JAVA循环获取值

循环遍历结构数组并将值放入xml中

T-SQL 循环查询结果

使用哈巴狗每次循环遍历一组对象

Swift iOS - 我为啥可以循环遍历一组类对象并进行属性更改但不能更改结构[重复]