Utilisation d'un curseur avec SQL dynamique dans une procédure stockée
-
20-08-2019 - |
Question
J'ai créé une instruction SQL dynamique dans une procédure stockée. Je dois parcourir les résultats à l'aide d'un curseur. J'ai du mal à trouver la bonne syntaxe. Voici ce que je fais.
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
Quelle est la bonne façon de faire cela?
La solution
Un curseur n'acceptera qu'une instruction select. Par conséquent, si le code SQL doit réellement être dynamique, placez le curseur de déclaration sur l'instruction que vous exécutez. Pour que le système ci-dessous fonctionne, votre serveur devra utiliser des curseurs globaux.
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
Si vous devez éviter d'utiliser les curseurs globaux, vous pouvez également insérer les résultats de votre code SQL dynamique dans une table temporaire, puis utiliser cette table pour renseigner votre curseur.
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
Autres conseils
Ce code est un très bon exemple de colonne dynamique avec un curseur, car vous ne pouvez pas utiliser '+' dans @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
Travailler avec une base de données non relationnelle (IDMS, quelqu'un?) sur une connexion ODBC est considéré comme l'un des cas où les curseurs et le code SQL dynamique semblent constituer l'unique voie.
select * from a where a=1 and b in (1,2)
prend 45 minutes pour répondre lorsqu'il est réécrit pour utiliser des jeux de clés sans la clause in s'exécutera dans moins d'une seconde:
select * from a where (a=1 and b=1)
union all
select * from a where (a=1 and b=2)
Si l'instruction in de la colonne B contient 1145 lignes, l'utilisation d'un curseur pour créer des instructions individuelles et les exécuter en tant que code SQL dynamique est beaucoup plus rapide que l'utilisation de la clause in. Silly hey?
Et oui, il n'y a pas de temps dans une base de données relationnelle pour que le curseur soit utilisé. Je ne peux tout simplement pas croire que je suis tombé sur un cas où une boucle de curseur est plusieurs magnitudes plus rapide.
Tout d’abord, évitez autant que possible d’utiliser un curseur. Voici quelques ressources pour résoudre le problème quand il semble que vous ne pouvez pas vous en passer:
Il doit y avoir 15 façons de perdre vos curseurs ... partie 1, Introduction
Traitement ligne par ligne sans curseur
Cela dit, vous risquez peut-être de rester coincé l'un après l'autre - je ne connais pas suffisamment votre question pour être sûr que l'un ou l'autre de ces critères s'applique. Si tel est le cas, le problème est différent: l'instruction select de votre curseur doit être une instruction SELECT actuelle , et non une instruction EXECUTE. Vous êtes coincé.
Mais voyez la réponse de cmsjr (qui est arrivée pendant que j’écrivais) sur l’utilisation d’une table temporaire. J'éviterais les curseurs globaux encore plus que & "; Plain &"; ceux ....
Après avoir récemment basculé d'Oracle vers SQL Server (préférence de l'employeur), j'ai remarqué que la prise en charge du curseur dans SQL Server était en retard. Les curseurs ne sont pas toujours diaboliques, ils sont parfois nécessaires, parfois beaucoup plus rapides et parfois plus propres que d'essayer de régler une requête complexe en réorganisant ou en ajoutant des indicateurs d'optimisation. Les & "; Les curseurs sont diaboliques &"; l'opinion est beaucoup plus importante dans la communauté SQL Server.
Donc, je suppose que cette réponse consiste à passer à Oracle ou à donner un indice à MS.
- Oracle EXECUTE IMMEDIATE dans un curseur
- Parcourez un curseur implicite (une
for
boucle implicite définit / ouvre / ferme le curseur!)
Il y a un autre exemple que j'aimerais partager avec vous
: D
http://www.sommarskog.se/dynamic_sql.html#cursor0
Une autre option de SQL Server consiste à effectuer toutes vos requêtes dynamiques dans une variable de table dans une procédure stockée, puis à utiliser un curseur pour interroger et traiter cette requête. En ce qui concerne le débat sur le curseur redouté :), j'ai vu des études montrant que dans certaines situations, un curseur peut être plus rapide s'il est correctement configuré. Je les utilise moi-même lorsque la requête requise est trop complexe, ou tout simplement pas humainement (pour moi;)) possible.
ce code peut vous être utile.
exemple d'utilisation du curseur sur le serveur 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
...