2012-09-04 8 views
10

In T-SQL, quando si ripetono i risultati da un cursore, sembra essere prassi comune ripetere l'istruzione FETCH prima del ciclo WHILE. L'esempio che segue da Microsoft:Come evitare duplicati FETCH in T-SQL quando si utilizza un cursore?

DECLARE Employee_Cursor CURSOR FOR 
SELECT EmployeeID, Title FROM AdventureWorks2012.HumanResources.Employee 
    WHERE JobTitle = 'Marketing Specialist'; 
OPEN Employee_Cursor; 

FETCH NEXT FROM Employee_Cursor; 
WHILE @@FETCH_STATUS = 0 
    BEGIN 
     FETCH NEXT FROM Employee_Cursor; 
    END; 
CLOSE Employee_Cursor; 
DEALLOCATE Employee_Cursor; 
GO 

(. Notate come FETCH NEXT FROM Employee_Cursor; appare due volte)

Se il FETCH seleziona in un lungo elenco di variabili, allora abbiamo una grande dichiarazione duplicato che è sia brutto e, naturalmente, , codice "non-DRY".

Io non sono a conoscenza di un'istruzione T-SQL controllo del flusso post-condizione così sembra che avrei dovuto ricorrere ad un WHILE(TRUE) e poi BREAK quando @@FETCH_STATUS non è pari a zero. Questo mi sembra schifoso.

Quali altre opzioni ho?

+2

Nel codice è mostrato, sostituire prima 'FETCH AVANTI DA Employee_Cursor' con' GOTO Employee_Cursor_Fetch' e luogo di etichetta 'Employee_Cursor_Fetch:' subito prima di rimanere 'FETCH NEXT'. Puoi notare che il nome dell'etichetta è derivato dal nome del cursore ('Employee_Cursor') per evitare di pensare ai nomi delle etichette. – miroxlav

risposta

-3

Detto semplicemente che non puoi ... è proprio il modo in cui la maggior parte delle istruzioni SQL funzionano. È necessario ottenere la prima riga prima del ciclo e quindi eseguire di nuovo l'istruzione while.

La domanda migliore come sbarazzarsi del cursore e cercare di risolvere la query senza di essa.

+1

Fortemente in disaccordo. Puoi evitare la duplicazione, anche se rimuovere il cursore è anche un obiettivo degno. –

+0

Mentre sono d'accordo che nella maggior parte dei casi i cursori dovrebbero essere evitati a favore di operazioni basate su set, ci sono alcuni casi in cui un cursore è assolutamente necessario. Vale a dire. Dove le righe devono essere valutate su base riga per riga e il risultato delle righe lette in precedenza determinerà il risultato delle letture successive. – Nick

4

Il primo Fetch non deve essere un Fetch next, solo uno fetch.

Quindi non ti stai ripetendo.

avrei passato uno sforzo maggiore per liberarsi del cursore, e meno su dogmi SECCO, (ma se è veramente importante, è possibile utilizzare un GOTO :) - Siamo spiacenti, M. Dijkstra)

GOTO Dry 
WHILE @@FETCH_STATUS = 0 
BEGIN 
    --- stuff here 

Dry: 
    FETCH NEXT FROM Employee_Cursor; 
END; 
+0

Sono totalmente d'accordo con la posizione sui cursori. Purtroppo, devo permettere che alcune righe falliscano senza fallire l'intero aggiornamento.Ho avuto il mio insert-in-select all done e quindi ho dovuto ricorrere ai cursori per ignorare le conversioni non riuscite nella mia selezione. Ho ronzio. –

+2

@BernhardHofmann Non puoi intercettare preventivamente i guasti con una clausola WHERE? – podiluska

6

Questo è quello che ho fatto ricorso a (oh la vergogna di esso):

WHILE (1=1) 
BEGIN 
    FETCH NEXT FROM C1 INTO 
    @foo, 
    @bar, 
    @bufar, 
    @fubar, 
    @bah, 
    @fu, 
    @foobar, 
    @another, 
    @column, 
    @in, 
    @the, 
    @long, 
    @list, 
    @of, 
    @variables, 
    @used, 
    @to, 
    @retrieve, 
    @all, 
    @values, 
    @for, 
    @conversion 

    IF (@@FETCH_STATUS <> 0) 
    BEGIN 
     BREAK 
    END 

    -- Use the variables here 
END 

CLOSE C1 
DEALLOCATE C1 

Si può capire perché ho postato una domanda. Non mi piace come il controllo del flusso sia nascosto in una dichiarazione if quando dovrebbe essere nello while.

+2

Penso che, tecnicamente, questa dovrebbe essere la risposta accettata, in quanto evita di duplicare la dichiarazione "FETCH". – Jeroen

-2

È ovvio che un cursore è il puntatore alla riga corrente nel recordset. Ma il semplice puntamento non ha senso se non può essere usato. Ecco la dichiarazione Fetch nella scena. Questo prende i dati dal recordset, li memorizza nelle variabili fornite. quindi se rimuovi la prima istruzione fetch il ciclo while non funzionerà poiché non c'è il record "FETCHED" per la manipolazione, se rimuovi l'ultima istruzione fetch, il "while" non eseguirà il loop-through.

Quindi è necessario che entrambe le istruzioni di recupero eseguano il loop-through del recordset completo.

+0

Non è necessario (vedere la mia risposta sopra). Stavo chiedendo modi migliori per evitare di scrivere il controllo del flusso, non se ho bisogno di avere il recupero due volte. –

8

C'è una buona struttura pubblicata online da Chris Oldwood che fa molto elegantemente:

DECLARE @done bit = 0 

WHILE (@done = 0) 
BEGIN 
    -- Get the next author. 
    FETCH NEXT FROM authors_cursor 
    INTO @au_id, @au_fname, @au_lname 

    IF (@@FETCH_STATUS <> 0) 
    BEGIN 
    SET @done = 1 
    CONTINUE 
    END 

    -- 
    -- stuff done here with inner cursor elided 
    -- 
END 
Problemi correlati