Utilizzo di un cursore con SQL dinamico in una procedura memorizzata
-
20-08-2019 - |
Domanda
Ho un'istruzione SQL dinamica che ho creato in una procedura memorizzata. Devo scorrere i risultati usando un cursore. Sto facendo fatica a capire la sintassi giusta. Ecco cosa sto facendo.
SELECT @SQLStatement = 'SELECT userId FROM users'
DECLARE @UserId
DECLARE users_cursor CURSOR FOR
EXECUTE @SQLStatment --Fails here. Doesn't like this
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC asp_DoSomethingStoredProc @UserId
END
CLOSE users_cursor
DEALLOCATE users_cursor
Qual è il modo giusto per farlo?
Soluzione
Un cursore accetterà solo un'istruzione select, quindi se l'SQL ha davvero bisogno di essere dinamico, rendi il cursore dichiarare parte dell'istruzione che stai eseguendo. Perché il seguito funzioni, il tuo server dovrà utilizzare i cursori globali.
Declare @UserID varchar(100)
declare @sqlstatement nvarchar(4000)
--move declare cursor into sql to be executed
set @sqlstatement = 'Declare users_cursor CURSOR FOR SELECT userId FROM users'
exec sp_executesql @sqlstatement
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId
WHILE @@FETCH_STATUS = 0
BEGIN
Print @UserID
EXEC asp_DoSomethingStoredProc @UserId
FETCH NEXT FROM users_cursor --have to fetch again within loop
INTO @UserId
END
CLOSE users_cursor
DEALLOCATE users_cursor
Se devi evitare di usare i cursori globali, puoi anche inserire i risultati del tuo SQL dinamico in una tabella temporanea, quindi utilizzare quella tabella per popolare il cursore.
Declare @UserID varchar(100)
create table #users (UserID varchar(100))
declare @sqlstatement nvarchar(4000)
set @sqlstatement = 'Insert into #users (userID) SELECT userId FROM users'
exec(@sqlstatement)
declare users_cursor cursor for Select UserId from #Users
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC asp_DoSomethingStoredProc @UserId
FETCH NEXT FROM users_cursor
INTO @UserId
END
CLOSE users_cursor
DEALLOCATE users_cursor
drop table #users
Altri suggerimenti
Questo codice è un ottimo esempio per una colonna dinamica con un cursore, poiché non è possibile utilizzare '+' in @STATEMENT:
ALTER PROCEDURE dbo.spTEST
AS
SET NOCOUNT ON
DECLARE @query NVARCHAR(4000) = N'' --DATA FILTER
DECLARE @inputList NVARCHAR(4000) = ''
DECLARE @field sysname = '' --COLUMN NAME
DECLARE @my_cur CURSOR
EXECUTE SP_EXECUTESQL
N'SET @my_cur = CURSOR FAST_FORWARD FOR
SELECT
CASE @field
WHEN ''fn'' then fn
WHEN ''n_family_name'' then n_family_name
END
FROM
dbo.vCard
WHERE
CASE @field
WHEN ''fn'' then fn
WHEN ''n_family_name'' then n_family_name
END
LIKE ''%''+@query+''%'';
OPEN @my_cur;',
N'@field sysname, @query NVARCHAR(4000), @my_cur CURSOR OUTPUT',
@field = @field,
@query = @query,
@my_cur = @my_cur OUTPUT
FETCH NEXT FROM @my_cur INTO @inputList
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT @inputList
FETCH NEXT FROM @my_cur INTO @inputList
END
RETURN
Lavorare con un database non relazionale (chiunque IDMS?) su una connessione ODBC si qualifica come una di quelle volte in cui cursori e SQL dinamico sembrano l'unica strada.
select * from a where a=1 and b in (1,2)
impiega 45 minuti per rispondere mentre riscritto per usare i keyset senza la clausola in verrà eseguito in meno di 1 secondo:
select * from a where (a=1 and b=1)
union all
select * from a where (a=1 and b=2)
Se l'istruzione in per la colonna B contiene 1145 righe, l'utilizzo di un cursore per creare istruzioni indipendenti ed eseguirle come SQL dinamico è molto più veloce rispetto all'utilizzo della clausola in. Ehi sciocco?
E sì, non c'è tempo in un database relazionale che il cursore debba essere usato. Non riesco proprio a credere di essermi imbattuto in un'istanza in cui un loop del cursore è più veloce di molte magnitudini.
Prima di tutto, evita di usare un cursore se possibile. Ecco alcune risorse per sradicarlo quando sembra che non puoi farne a meno:
Devono esserci 15 modi per perdere i cursori ... parte 1, Introduzione
Elaborazione riga per riga senza cursore
Detto questo, però, potresti essere bloccato con uno dopo tutto - non so abbastanza dalla tua domanda per essere sicuro che uno di questi si applichi. In tal caso, hai un problema diverso: l'istruzione select per il cursore deve essere un'istruzione SELECT effettiva , non un'istruzione EXECUTE. Sei bloccato.
Ma vedi la risposta di cmsjr (che è arrivata mentre stavo scrivendo) sull'uso di una tabella temporanea. Eviterei i cursori globali anche più di & Quot; plain & Quot; quelli ....
Dopo il recente passaggio da Oracle a SQL Server (preferenza del datore di lavoro), noto che il supporto del cursore in SQL Server è in ritardo. I cursori non sono sempre malvagi, a volte richiesti, a volte molto più veloci e talvolta più puliti rispetto al tentativo di ottimizzare una query complessa riorganizzando o aggiungendo suggerimenti di ottimizzazione. I & Quot; i cursori sono malvagi & Quot; L'opinione è molto più importante nella comunità di SQL Server.
Quindi suppongo che questa risposta sia passare a Oracle o dare un indizio a MS.
- Oracle EXECUTE IMMEDIATE in un cursore
- Passa attraverso un cursore implicito (un
for
loop implicitamente definisce / apre / chiude il cursore!)
C'è un altro esempio che vorrei condividere con te
: D
http://www.sommarskog.se/dynamic_sql.html#cursor0
Un'altra opzione in SQL Server è di eseguire tutte le query dinamiche in una variabile di tabella in un proc memorizzato, quindi utilizzare un cursore per eseguire query ed elaborarlo. Per quanto riguarda il temuto dibattito sul cursore :), ho visto studi che mostrano che in alcune situazioni, un cursore può effettivamente essere più veloce se impostato correttamente. Li uso io stesso quando la query richiesta è troppo complessa o semplicemente non umanamente (per me;)) possibile.
questo codice può essere utile per te.
esempio di utilizzo del cursore nel server sql
DECLARE sampleCursor CURSOR FOR
SELECT K.Id FROM TableA K WHERE ....;
OPEN sampleCursor
FETCH NEXT FROM sampleCursor INTO @Id
WHILE @@FETCH_STATUS <> -1
BEGIN
UPDATE TableB
SET
...