Loop do SQL Server - como percorrer um conjunto de registros

151

como percorrer um conjunto de registros de uma seleção?

Por exemplo, digamos que eu tenho alguns registros que desejo percorrer e fazer algo com cada registro. Aqui está uma versão primitiva do meu select:

select top 1000 * from dbo.table
where StatusID = 7 

obrigado

Funky
fonte
5
O que você quer fazer para cada registro? A preferência seria fazer o trabalho em uma consulta SQL. Exceto que você precisaria usar o T-SQL, talvez com cursores.
Gordon Linoff
2
Eu usaria um cursor.
FloChanz
5
Isso será bem lento - não é possível reescrever o processo armazenado ou mover parte da lógica dele para funcionar de maneira baseada em conjunto?
Ponte
2
@ Funky o que o sproc faz? Freqüentemente, o código pode ser reescrito de maneira baseada em conjunto (por exemplo, evitar loops). Se você é inflexível, deseja executar uma operação RBAR ( simple-talk.com/sql/t-sql-programming/… ), então um cursor é o que você deseja investigar.
gvee
1
Talvez você possa explicar o que fará com esses dados com mais detalhes. Na maioria dos casos, você pode escrever facilmente uma única consulta SQL que fará o que você precisa para executar uma ação em vez de fazer um loop por registros individuais.
Alan Barber

Respostas:

212

Usando T-SQL e cursores como este:

DECLARE @MyCursor CURSOR;
DECLARE @MyField YourFieldDataType;
BEGIN
    SET @MyCursor = CURSOR FOR
    select top 1000 YourField from dbo.table
        where StatusID = 7      

    OPEN @MyCursor 
    FETCH NEXT FROM @MyCursor 
    INTO @MyField

    WHILE @@FETCH_STATUS = 0
    BEGIN
      /*
         YOUR ALGORITHM GOES HERE   
      */
      FETCH NEXT FROM @MyCursor 
      INTO @MyField 
    END; 

    CLOSE @MyCursor ;
    DEALLOCATE @MyCursor;
END;
FloChanz
fonte
5
O certo é reescrever o processo para que ele não precise ser repetido. Looping é uma escolha extremamente ruim em um banco de dados.
HLGEM
23
Talvez você esteja certo, mas com as informações fornecidas na pergunta no momento em que escrevi a resposta, o usuário apenas deseja percorrer um conjunto de dados ... e um Cursor é uma maneira de fazê-lo.
FloChanz
16
Os cursores são apenas uma ferramenta - nada geralmente certo ou errado sobre eles. Observe o desempenho e decida. Essa resposta (cursores) é uma opção possível. Você também pode usar WHILE LOOP, CTE, etc.
Chains
2
@FrenkyB Sim, você pode. Olhe desta maneira ... stackoverflow.com/questions/11035187/…
sam yi
2
Parabéns, sua solução ainda está em msdn : msdn.microsoft.com/en-us/library/… e eu gosto muito de como você usa o Tipo de dados de campo.
Pete
111

É o que eu tenho feito se você precisar fazer algo iterativo ... mas seria prudente procurar primeiro as operações definidas.

select top 1000 TableID
into #ControlTable 
from dbo.table
where StatusID = 7

declare @TableID int

while exists (select * from #ControlTable)
begin

    select top 1 @TableID = TableID
    from #ControlTable
    order by TableID asc

    -- Do something with your TableID

    delete #ControlTable
    where TableID = @TableID

end

drop table #ControlTable
sam yi
fonte
4
Usar um CURSOR (veja a resposta abaixo) parece ser uma solução muito mais elegante.
Mikhail Glukhov,
Por que esta resposta tem mais votos positivos do que a solução do cursor?
Ataravati
29
@ataravati Porque esta solução é mais limpa para muitos programadores do que os cursores. A sintaxe para os cursores é bastante estranha para alguns.
9788 Brian Kelvin
Obrigado! Meu exemplo com atualização e agrupamento por lógica usando o código acima: pastebin.com/GAjUNNi9 . Talvez seja útil para qualquer pessoa.
Nigrimmist
a variável pode ser usada como um nome de coluna na instrução update dentro do loop? Algo como "Atualizar TableName SET @ ColumnName = 2"
MH
28

Pequena alteração na resposta de sam yi (para melhor legibilidade):

select top 1000 TableID
into #ControlTable 
from dbo.table
where StatusID = 7

declare @TableID int

while exists (select * from #ControlTable)
begin

    select @TableID = (select top 1 TableID
                       from #ControlTable
                       order by TableID asc)

    -- Do something with your TableID

    delete #ControlTable
    where TableID = @TableID

end

drop table #ControlTable
Preceito
fonte
1
@bluish, esta resposta está corrigindo a resposta de sam yi. Essa correção está principalmente dentro da select @TableID = (...)declaração.
Simple Sandman
Eu acho que essa resposta deve ser selecionado desta questão
sajadre
14

Ao usar o cursor, você pode facilmente percorrer os registros individualmente e imprimir registros separadamente ou como uma única mensagem, incluindo todos os registros.

DECLARE @CustomerID as INT;
declare @msg varchar(max)
DECLARE @BusinessCursor as CURSOR;

SET @BusinessCursor = CURSOR FOR
SELECT CustomerID FROM Customer WHERE CustomerID IN ('3908745','3911122','3911128','3911421')

OPEN @BusinessCursor;
    FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
    WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @msg = '{
              "CustomerID": "'+CONVERT(varchar(10), @CustomerID)+'",
              "Customer": {
                "LastName": "LastName-'+CONVERT(varchar(10), @CustomerID) +'",
                "FirstName": "FirstName-'+CONVERT(varchar(10), @CustomerID)+'",    
              }
            }|'
        print @msg
    FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
END
Agnel Amodia
fonte
1
isso parece interessante. Gostaria de saber o que o identificador @ significa.
Netskink 24/10/19
@ é apenas para diferenciar como variáveis.
precisa
9

Apenas outra abordagem, se você estiver bem usando tabelas temporárias. Eu testei isso pessoalmente e não causará nenhuma exceção (mesmo que a tabela temporária não possua nenhum dado).

CREATE TABLE #TempTable
(
    ROWID int identity(1,1) primary key,
    HIERARCHY_ID_TO_UPDATE int,
)

--create some testing data
--INSERT INTO #TempTable VALUES(1)
--INSERT INTO #TempTable VALUES(2)
--INSERT INTO #TempTable VALUES(4)
--INSERT INTO #TempTable VALUES(6)
--INSERT INTO #TempTable VALUES(8)

DECLARE @MAXID INT, @Counter INT

SET @COUNTER = 1
SELECT @MAXID = COUNT(*) FROM #TempTable

WHILE (@COUNTER <= @MAXID)
BEGIN
    --DO THE PROCESSING HERE 
    SELECT @HIERARCHY_ID_TO_UPDATE = PT.HIERARCHY_ID_TO_UPDATE
    FROM #TempTable AS PT
    WHERE ROWID = @COUNTER

    SET @COUNTER = @COUNTER + 1
END


IF (OBJECT_ID('tempdb..#TempTable') IS NOT NULL)
BEGIN
    DROP TABLE #TempTable
END
Sandeep
fonte
Isso é realmente estranho. Ele contém muitos erros, também o uso de duas variáveis ​​em que uma passa de 1 para COUNT(*)e a segunda passa COUNT(*)para 1 é estranho.
David Ferenczy Rogožan
A variável MAXID é usada para LOOP. A variável COUNTER é usada para executar uma operação em um registro específico na tabela. Se eu leio a pergunta, ela fala sobre "tem alguns registros que eu gostaria de percorrer e fazer algo com cada registro". Posso estar errado, mas por favor, apontar o que está errado @DAWID acima
Sandeep
2
Eu acho óbvio como você usa essas variáveis ​​no seu código. Você pode simplesmente ter WHILE (@COUTNER <= @ROWID)e não precisa diminuir @ROWIDem cada iteração. Entre o que acontece se ROWIDs na sua tabela não forem contínuos (algumas linhas foram excluídas anteriormente).
David Ferenczy Rogožan
1
Quando você sugeriria usar uma tabela temporária em vez de usar um cursor? Isso é apenas uma escolha de design ou alguém tem melhor desempenho?
H0r53
4

Você pode escolher classificar seus dados e adicionar um ROW_NUMBER e fazer a contagem regressiva até zero enquanto itera seu conjunto de dados.

-- Get your dataset and rank your dataset by adding a new row_number
SELECT  TOP 1000 A.*, ROW_NUMBER() OVER(ORDER BY A.ID DESC) AS ROW
INTO #TEMPTABLE 
FROM DBO.TABLE AS A
WHERE STATUSID = 7;

--Find the highest number to start with
DECLARE @COUNTER INT = (SELECT MAX(ROW) FROM #TEMPTABLE);
DECLARE @ROW INT;

-- Loop true your data until you hit 0
WHILE (@COUNTER != 0)
BEGIN

    SELECT @ROW = ROW
    FROM #TEMPTABLE
    WHERE ROW = @COUNTER
    ORDER BY ROW DESC

    --DO SOMTHING COOL  

    -- SET your counter to -1
    SET @COUNTER = @ROW -1
END

DROP TABLE #TEMPTABLE
Bunkerbuster
fonte
2

Dessa forma, podemos iterar nos dados da tabela.

DECLARE @_MinJobID INT
DECLARE @_MaxJobID INT
CREATE  TABLE #Temp (JobID INT)

INSERT INTO #Temp SELECT * FROM DBO.STRINGTOTABLE(@JobID,',')
SELECT @_MinJID = MIN(JobID),@_MaxJID = MAX(JobID)  FROM #Temp

    WHILE @_MinJID <= @_MaxJID
    BEGIN

        INSERT INTO Mytable        
        (        
            JobID,        
        )        

        VALUES        
        (        
            @_MinJobID,        
        ) 

        SET @_MinJID = @_MinJID + 1;
    END

DROP TABLE #Temp

STRINGTOTABLE é uma função de definição do usuário que analisa dados separados por vírgula e retorna a tabela. obrigado

Monojit Sarkar
fonte
1

Eu acho que este é o exemplo da maneira mais fácil de iterar item.

declare @cateid int
select CateID into [#TempTable] from Category where GroupID = 'STOCKLIST'

while (select count(*) from #TempTable) > 0
begin
    select top 1 @cateid = CateID from #TempTable
    print(@cateid)

    --DO SOMETHING HERE

    delete #TempTable where CateID = @cateid
end

drop table #TempTable
江明哲
fonte