web-dev-qa-db-fra.com

Utilisation d'un curseur avec SQL dynamique dans une procédure stockée

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?

46
Micah

Un curseur accepte uniquement une instruction select. Par conséquent, si le code SQL doit être dynamique, intégrez le curseur de déclaration à 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 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
110
cmsjr

Ce code est un très bon exemple pour une 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
19
SMHMayboudi

Travailler avec une base de données non relationnelle (IDMS, quelqu'un?) Sur une connexion ODBC) est l’un des cas où les curseurs et le code SQL dynamique semblent constituer le seul chemin.

select * from a where a=1 and b in (1,2)

cela prend 45 minutes pour répondre en réécrivant pour utiliser des jeux de clés sans que la clause in s'exécute en 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 où le curseur doit être utilisé. Je ne peux tout simplement pas croire que je sois tombé sur un cas où une boucle de curseur est plusieurs magnitudes plus rapide.

4
Twelfth

Tout d'abord, évitez d'utiliser un curseur si possible. 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 vous en prendre une après tout - je ne connais pas suffisamment votre question pour être sûre que l'une ou l'autre de ces conditions s'applique. Si tel est le cas, vous avez un problème différent: l'instruction select de votre curseur doit être une instruction SELECT réelle et non une instruction EXECUTE. Tu es coincé.

Mais voyez la réponse de cmsjr (qui est arrivée pendant que j'écrivais) sur l'utilisation d'une table temporaire. Je voudrais éviter les curseurs globaux encore plus que les curseurs "simples" ....

3
RolandTumble

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 "curseurs sont mauvais" l'opinion est beaucoup plus importante dans la communauté SQL Server.

Je suppose donc que cette réponse consiste à passer à Oracle ou à donner un indice à MS.

1
crokusek

Il y a un autre exemple que j'aimerais partager avec vous
: D http://www.sommarskog.se/dynamic_sql.html#cursor

1
SMHMayboudi

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 dernière. En ce qui concerne le débat sur le curseur redouté :), j'ai vu des études qui montrent 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.

0
davaus