95

I run a query select @id=table.id from table and I need to loop over the results so I can exec a store procedure for each row exec stored_proc @varName=@id,@otherVarName='test'

How can I do this in a T-SQL script?

Justin808
  • 20,859
  • 46
  • 160
  • 265
  • well, I didn't try anything. I don't know how do do it. I suppose I could try to write a `C` style loop, but I doubt that will work. Maybe a `PHP` style loop, but again I doubt that will work too. I need to do it all in T-SQL in that I can run the SQL in SMS without an external language doing the looping... Hense the question in the first place. – Justin808 Aug 07 '12 at 19:35
  • 2
    @Shedal The irony of those comments/answers (though less than the "google it" answers) is that, over time they tend to become top google results. Thus in turn, when someone searches for a solution to the some problem, the first thing he will come across is a bashing (usually justified) of someone else who had the problem, but who didn't bother to search for the answer himself. In the long run, it becomes annoying to people who actually try to search for the answer. – jahu May 28 '14 at 10:14
  • @MarcinHabuszewski well, to be honest, in case there is no proper answer for a common question, it won't show up on the first page of Google search results. Normally. – Dmytro Shevchenko May 28 '14 at 13:38

7 Answers7

205

You could use a CURSOR in this case:

DECLARE @id INT
DECLARE @name NVARCHAR(100)
DECLARE @getid CURSOR

SET @getid = CURSOR FOR
SELECT table.id,
       table.name
FROM   table

OPEN @getid
FETCH NEXT
FROM @getid INTO @id, @name
WHILE @@FETCH_STATUS = 0
BEGIN
    EXEC stored_proc @varName=@id, @otherVarName='test', @varForName=@name
    FETCH NEXT
    FROM @getid INTO @id, @name
END

CLOSE @getid
DEALLOCATE @getid

Modified to show multiple parameters from the table.

XN16
  • 5,679
  • 15
  • 48
  • 72
26

You could do something like this:

create procedure test
as
BEGIN

    create table #ids
    (
        rn int,
        id int
    )
    
    insert into #ids (rn, id)
    select distinct row_number() over(order by id) as rn, id
    from table
    
    declare @id int
    declare @totalrows int = (select count(*) from #ids)
    declare @currentrow int = 0
    
    while @currentrow <  @totalrows  
    begin 
        set @id = (select id from #ids where rn = @currentrow + 1)
          
        exec stored_proc @varName=@id, @otherVarName='test'
        
        set @currentrow = @currentrow +1
    end  

END
Greg R Taylor
  • 3,470
  • 1
  • 25
  • 19
Taryn
  • 242,637
  • 56
  • 362
  • 405
  • I like this method better since cursor are slower than while loop – poudigne Nov 09 '15 at 15:00
  • 3
    Cursors are slower when compared to set operations (i.e. crafting one big select/update statement). When compared to creating a temporary table and doing some _writes_ I am not so sure. See http://www.techrepublic.com/blog/the-enterprise-cloud/comparing-cursor-vs-while-loop-performance-in-sql-server-2008/ I did my own tests and I concur. Possibly a temp table is faster when you have locking issues and you can get your subset faster and then process it. It depends I guess... – phil_w May 10 '16 at 13:31
  • On SQL Server this gave me an error on the first iteration of the loop because row_number() starts from 1 but @currentrow is initialized to 0 – samgak Jan 30 '20 at 02:32
10

My prefer solution is Microsoft KB 111401 http://support.microsoft.com/kb/111401.

The link refers to 3 examples:

This article describes various methods that you can use to simulate a cursor-like FETCH-NEXT logic in a stored procedure, trigger, or Transact-SQL batch.

/*********** example 1 ***********/ 

declare @au_id char( 11 )

set rowcount 0
select * into #mytemp from authors

set rowcount 1

select @au_id = au_id from #mytemp

while @@rowcount <> 0
begin
    set rowcount 0
    select * from #mytemp where au_id = @au_id
    delete #mytemp where au_id = @au_id

    set rowcount 1
    select @au_id = au_id from #mytemp
end
set rowcount 0



/********** example 2 **********/ 

declare @au_id char( 11 )

select @au_id = min( au_id ) from authors

while @au_id is not null
begin
    select * from authors where au_id = @au_id
    select @au_id = min( au_id ) from authors where au_id > @au_id
end



/********** example 3 **********/ 

set rowcount 0
select NULL mykey, * into #mytemp from authors

set rowcount 1
update #mytemp set mykey = 1

while @@rowcount > 0
begin
    set rowcount 0
    select * from #mytemp where mykey = 1
    delete #mytemp where mykey = 1
    set rowcount 1
    update #mytemp set mykey = 1
end
set rowcount 0
billybob
  • 2,859
  • 6
  • 35
  • 55
user1965719
  • 489
  • 5
  • 4
3
DECLARE @id INT
DECLARE @name NVARCHAR(100)
DECLARE @getid CURSOR

SET @getid = CURSOR FOR
SELECT table.id,
       table.name
FROM   table

WHILE 1=1
BEGIN

    FETCH NEXT
    FROM @getid INTO @id, @name
    IF @@FETCH_STATUS < 0 BREAK

    EXEC stored_proc @varName=@id, @otherVarName='test', @varForName=@name

END

CLOSE @getid
DEALLOCATE @getid
TeoB
  • 31
  • 1
0

try this:

declare @i tinyint = 0,
    @count tinyint,
    @id int,
    @name varchar(max)

select @count = count(*) from table
while (@i < @count)
begin
    select @id = id, @name = name from table
    order by nr asc offset @i rows fetch next 1 rows only

    exec stored_proc @varName = @id, @otherVarName = 'test', @varForName = @name

    set @i = @i + 1
end
0
DECLARE @id INT
DECLARE @filename NVARCHAR(100)
DECLARE @getid CURSOR

SET @getid = CURSOR FOR
SELECT top 3 id,
filename 
FROM  table

OPEN @getid
WHILE 1=1
BEGIN

    FETCH NEXT
    FROM @getid INTO @id, @filename
    IF @@FETCH_STATUS < 0 BREAK

    print @id

END


CLOSE @getid
DEALLOCATE @getid
user2321864
  • 2,207
  • 5
  • 25
  • 35
akshay k.k
  • 103
  • 1
  • 6
0

I did not find more or less direct solution here, so I needed to use other resources and modify the code a little bit.

DECLARE @TrusteeElectionId INT;
    INSERT INTO dbo.TrusteeElections (CorpActId, ElectedOption )
    VALUES(@CorpActId,@ElectedOption)
    SET @trusteeElectionId = (select scope_identity())

    create table #ProductWrapperTypeIds
    (
        Id int
    )

     insert into #ProductWrapperTypeIds (id)
     SELECT value FROM STRING_SPLIT(@ProductWrapperTypes, ',') -- @ProductWrapperTypes is a story procedure parameter that looks like that '12,13,14' -- as a result of the code above #ProductWrapperTypeIds will have table with int IDs.


    declare @id int
    declare @totalrows int = (select count(*) from #ProductWrapperTypeIds)
    declare @currentId int = (SELECT MIN(Id) FROM  #ProductWrapperTypeIds) -- make sure we start from smaller id
    declare @currentrow int = (SELECT 0) -- counter starts from 0
    
    while @currentrow <  @totalrows  
   begin
    SET @id = NULL;
    SET @id = (select id from #ProductWrapperTypeIds where id = @currentId);
    IF @id IS NOT NULL
        BEGIN
              INSERT INTO dbo.TrusteeElectionProductTypes (ProductWrapperTypeId,TrusteeElectionId ) -- we don't want to insert NULL
              VALUES(@id, @TrusteeElectionId)
              SET @currentrow = @currentrow +1 -- we increase counter only if we find an id
        END
        SET @currentId = @currentId + 1 -- we promote id to the next item anyway


end

This example covers the conditions code too.

Mr.B
  • 3,484
  • 2
  • 26
  • 40