301 votes

Existe-t-il un moyen de boucler sur une variable de table en TSQL sans utiliser de curseur ?

Disons que j'ai la variable tableau simple suivante :

declare @databases table
(
    DatabaseID    int,
    Name        varchar(15),   
    Server      varchar(15)
)
-- insert a bunch rows into @databases

La déclaration et l'utilisation d'un curseur sont-elles ma seule option si je voulais itérer dans les lignes ? Existe-t-il un autre moyen ?

3 votes

Bien que je ne sois pas sûr du problème que vous voyez avec l'approche ci-dessus ; Voyez si cela vous aide databasejournal.com/features/mssql/article.php/3111031

5 votes

Pourriez-vous nous donner la raison pour laquelle vous voulez itérer sur les lignes, d'autres solutions qui ne nécessitent pas d'itération peuvent exister (et qui sont plus rapides dans la plupart des cas).

0 votes

Je suis d'accord avec pop... on peut ne pas avoir besoin d'un curseur selon la situation. mais il n'y a aucun problème à utiliser des curseurs si on en a besoin.

443voto

Martynnw Points 3272

Tout d'abord, vous devez être absolument sûr que vous avez besoin d'itérer à travers chaque ligne - les opérations basées sur les ensembles seront plus rapides dans tous les cas auxquels je peux penser et utiliseront normalement un code plus simple.

En fonction de vos données, il peut être possible de boucler en utilisant seulement SELECT comme indiqué ci-dessous :

Declare @Id int

While (Select Count(*) From ATable Where Processed = 0) > 0
Begin
    Select Top 1 @Id = Id From ATable Where Processed = 0

    --Do some processing here

    Update ATable Set Processed = 1 Where Id = @Id 

End

Une autre solution consiste à utiliser une table temporaire :

Select *
Into   #Temp
From   ATable

Declare @Id int

While (Select Count(*) From #Temp) > 0
Begin

    Select Top 1 @Id = Id From #Temp

    --Do some processing here

    Delete #Temp Where Id = @Id

End

L'option que vous devez choisir dépend vraiment de la structure et du volume de vos données.

Note : Si vous utilisez SQL Server, il serait préférable d'utiliser :

WHILE EXISTS(SELECT * FROM #Temp)

Utilisation de COUNT devra toucher chaque ligne de la table, l'option EXISTS ne doit toucher que le premier (voir La réponse de Josef ci-dessous).

0 votes

"Select Top 1 @Id = Id From ATable" doit être "Select Top 1 @Id = Id From ATable Where Processed = 0".

10 votes

Si vous utilisez SQL Server, consultez la réponse de Josef ci-dessous pour une petite modification de ce qui précède.

3 votes

Pouvez-vous expliquer pourquoi cela est mieux que d'utiliser un curseur ?

153voto

Josef Points 4395

Juste une petite note, si vous utilisez SQL Server (2008 et plus), les exemples qui ont :

While (Select Count(*) From #Temp) > 0

Serait mieux servi avec

While EXISTS(SELECT * From #Temp)

Le comte devra toucher chaque ligne du tableau, le EXISTS n'a besoin de toucher que le premier.

13 votes

Ce n'est pas une réponse mais un commentaire/une amélioration de la réponse de Martynw.

7 votes

Le contenu de cette note nécessite une meilleure fonctionnalité de mise en forme qu'un commentaire, je suggère de l'annexer à la réponse.

2 votes

Dans les versions ultérieures de SQL, l'optimiseur de requêtes est suffisamment intelligent pour savoir que lorsque vous écrivez la première chose, vous voulez en fait dire la seconde et l'optimise en conséquence pour éviter le balayage de la table.

50voto

Trevor Points 91

C'est comme ça que je fais :

declare @RowNum int, @CustId nchar(5), @Name1 nchar(25)

select @CustId=MAX(USERID) FROM UserIDs     --start with the highest ID
Select @RowNum = Count(*) From UserIDs      --get total number of records
WHILE @RowNum > 0                          --loop until no more records
BEGIN   
    select @Name1 = username1 from UserIDs where USERID= @CustID    --get other info from that row
    print cast(@RowNum as char(12)) + ' ' + @CustId + ' ' + @Name1  --do whatever

    select top 1 @CustId=USERID from UserIDs where USERID < @CustID order by USERID desc--get the next one
    set @RowNum = @RowNum - 1                               --decrease count
END

Pas de curseurs, pas de tables temporaires, pas de colonnes supplémentaires. La colonne USERID doit être un nombre entier unique, comme la plupart des clés primaires.

34voto

Terrapin Points 15061

Définissez votre table temporaire comme ceci -

declare @databases table
(
    RowID int not null identity(1,1) primary key,
    DatabaseID    int,
    Name        varchar(15),   
    Server      varchar(15)
)

-- insert a bunch rows into @databases

Alors faites ceci -

declare @i int
select @i = min(RowID) from @databases
declare @max int
select @max = max(RowID) from @databases

while @i <= @max begin
    select DatabaseID, Name, Server from @database where RowID = @i --do some stuff
    set @i = @i + 1
end

19voto

leoinfo Points 3364

Voici comment je m'y prendrais :

Select Identity(int, 1,1) AS PK, DatabaseID
Into   #T
From   @databases

Declare @maxPK int;Select @maxPK = MAX(PK) From #T
Declare @pk int;Set @pk = 1

While @pk <= @maxPK
Begin

    -- Get one record
    Select DatabaseID, Name, Server
    From @databases
    Where DatabaseID = (Select DatabaseID From #T Where PK = @pk)

    --Do some processing here
    -- 

    Select @pk = @pk + 1
End

[Edit] Parce que j'ai probablement omis le mot "variable" lorsque j'ai lu la question la première fois, voici une réponse mise à jour...


declare @databases table
(
    PK            int IDENTITY(1,1), 
    DatabaseID    int,
    Name        varchar(15),   
    Server      varchar(15)
)
-- insert a bunch rows into @databases
--/*
INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MainDB', 'MyServer'
INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MyDB',   'MyServer2'
--*/

Declare @maxPK int;Select @maxPK = MAX(PK) From @databases
Declare @pk int;Set @pk = 1

While @pk <= @maxPK
Begin

    /* Get one record (you can read the values into some variables) */
    Select DatabaseID, Name, Server
    From @databases
    Where PK = @pk

    /* Do some processing here */
    /* ... */ 

    Select @pk = @pk + 1
End

5 votes

Donc, en fait, vous faites un curseur, mais sans tous les avantages d'un curseur.

1 votes

... sans verrouiller les tables qui sont utilisées pendant le traitement... car c'est l'un des aspects les plus importants de la gestion de l'information. avantages d'un curseur :)

3 votes

Les tables ? Il s'agit d'une table VARIABLE - il n'y a pas d'accès simultané possible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X