Ciclo di SQL Server: come posso scorrere un set di record


151

come faccio a scorrere un set di record da una selezione?

Ad esempio, per esempio, ho alcuni dischi che desidero ripetere e fare qualcosa con ogni disco. Ecco una versione primitiva della mia selezione:

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

Grazie


5
Cosa vuoi fare per ogni disco? La preferenza sarebbe quella di fare il lavoro in una query SQL. Salvo che avresti bisogno di usare T-SQL, forse con i cursori.
Gordon Linoff,

2
Vorrei usare un cursore.
FloChanz,

5
Sarà piuttosto lento - non è possibile riscrivere il proc memorizzato o spostare parte della logica da esso per funzionare in modo basato su set?
Bridge,

2
@Funky cosa fa lo sproc? Spesso il codice può essere riscritto in un modo basato su set (cioè evitare loop). Se sei irremovibile che vuoi eseguire un'operazione RBAR ( simple-talk.com/sql/t-sql-programming/… ), allora un cursore è ciò che vuoi investigare.
Gvee,

1
Forse puoi spiegare cosa farai con questi dati in modo più dettagliato. Nella maggior parte dei casi è possibile scrivere facilmente una singola query SQL che farà ciò che è necessario per eseguire in una sola azione invece di scorrere ciclicamente i singoli record.
Alan Barber,

Risposte:


213

Usando T-SQL e cursori come questo:

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;

5
La cosa giusta è riscrivere il processo in modo che non debba essere ripetuto. Il looping è una scelta estremamente negativa in un database.
HLGEM,

23
Forse hai ragione, ma con le informazioni fornite nella domanda al momento in cui ho scritto la risposta, l'utente vuole solo scorrere un set di dati ... e un cursore è un modo per farlo.
FloChanz,

16
I cursori sono solo uno strumento, in genere nulla di giusto o sbagliato su di loro. Osserva le prestazioni e decidi. Questa risposta (cursori) è una possibile scelta. Puoi anche usare un DURO LOOP, un CTE, ecc.
Catene

2
@FrenkyB Sì, puoi. Guarda in questo modo ... stackoverflow.com/questions/11035187/…
sam yi

2
Congratulazioni, la tua soluzione è persino su msdn: msdn.microsoft.com/en-us/library/… e mi piace molto come usi il tipo di dati sul campo.
Pete,

111

Questo è quello che ho fatto se hai bisogno di fare qualcosa di iterativo ... ma sarebbe saggio cercare prima le operazioni impostate.

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

4
L'uso di un CURSORE (vedi la risposta sotto) sembra essere una soluzione molto più elegante.
Mikhail Glukhov,

Perché questa risposta ha più voti positivi rispetto alla soluzione cursore?
Ataravati,

29
@ataravati Perché questa soluzione è più chiara per molti programmatori rispetto ai cursori. La sintassi per i cursori è piuttosto imbarazzante per alcuni.
Brian Webster,

Grazie! Il mio esempio con aggiornamento e raggruppa per logica usando il codice sopra: pastebin.com/GAjUNNi9 . Forse sarà utile a chiunque.
Nigrimmist,

la variabile può essere utilizzata come nome di colonna nell'istruzione update all'interno del ciclo? Qualcosa come "Aggiorna TableName SET @ ColumnName = 2"
MH

28

Piccola modifica alla risposta di sam yi (per una migliore leggibilità):

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

1
@bluish, questa risposta sta correggendo la risposta di sam yi. Questa correzione è principalmente all'interno della select @TableID = (...)dichiarazione.
Semplice Sandman,

Penso che questa risposta debba essere selezionata per questa domanda
sajadre,

14

Utilizzando il cursore è possibile scorrere facilmente i record singolarmente e stampare i record separatamente o come un singolo messaggio inclusi tutti i record.

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

1
questo sembra interessante. Mi chiedo cosa significhi l'identificatore @.
netskink,

@ è solo per differenziarsi come variabili.
Agnel Amodia,

9

Solo un altro approccio se stai bene usando le tabelle temporanee. L'ho testato personalmente e non causerà alcuna eccezione (anche se la tabella temporanea non ha dati).

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

Questo è davvero strano. Contiene molti errori, anche l'uso di due variabili in cui una va da 1 a COUNT(*)e la seconda da COUNT(*)1 a 1 è strano.
David Ferenczy Rogožan,

La variabile MAXID viene utilizzata per LOOP. La variabile COUNTER viene utilizzata per eseguire un'operazione su un particolare record nella tabella. Se leggo la domanda di cui parla "ho alcuni dischi che desidero passare in rassegna e fare qualcosa con ogni disco". Potrei sbagliarmi, ma per favore fai notare cosa c'è che non va sopra @DAWID
Sandeep

2
Penso che sia ovvio come usi queste variabili nel tuo codice. Puoi semplicemente avere WHILE (@COUTNER <= @ROWID)e non è necessario diminuire @ROWIDin ogni iterazione. A proposito, cosa succede se ROWIDi messaggi nella tabella non sono continui (alcune righe sono state precedentemente eliminate).
David Ferenczy Rogožan,

1
Quando consiglieresti di usare una tabella temporanea rispetto a un cursore? È solo una scelta progettuale o si ottengono prestazioni migliori?
h0r53,

4

Puoi scegliere di classificare i tuoi dati, aggiungere un ROW_NUMBER e contare fino a zero mentre esegui l'iterazione del tuo set di dati.

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

2

in questo modo possiamo scorrere i dati della tabella.

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 è la funzione definita dall'utente che analizzerà i dati separati da virgola e restituirà la tabella. Grazie


1

Penso che questo sia il semplice esempio per iterare l'oggetto.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.