Boucle SQL Server - comment parcourir un ensemble d'enregistrements

151

comment puis-je parcourir un ensemble d'enregistrements à partir d'une sélection?

Disons par exemple que j'ai quelques enregistrements que je souhaite parcourir et faire quelque chose avec chaque enregistrement. Voici une version primitive de ma sélection:

select top 1000 * from dbo.table
where StatusID = 7 

Merci

Froussard
la source
5
Que voulez-vous faire pour chaque enregistrement? La préférence serait de faire le travail dans une requête SQL. Sauf que vous auriez besoin d'utiliser T-SQL, peut-être avec des curseurs.
Gordon Linoff
2
J'utiliserais un curseur.
FloChanz
5
Ce sera assez lent - n'est-il pas possible de réécrire le processus stocké ou d'en déplacer une partie de la logique pour fonctionner d'une manière basée sur un ensemble?
Bridge
2
@Funky que fait le sproc? Souvent, le code peut être réécrit d'une manière basée sur un ensemble (c'est-à-dire éviter les boucles). Si vous êtes catégorique que vous souhaitez effectuer une opération RBAR ( simple-talk.com/sql/t-sql-programming/… ), alors un curseur est ce que vous voulez étudier.
gvee
1
Peut-être pouvez-vous expliquer plus en détail ce que vous allez faire avec ces données. Dans la plupart des cas, vous pouvez facilement écrire une seule requête SQL qui fera ce dont vous avez besoin en une seule action au lieu de parcourir des enregistrements individuels.
Alan Barber

Réponses:

213

En utilisant T-SQL et des curseurs comme celui-ci:

DECLARE @MyCursor CURSOR;
DECLARE @MyField YourFieldDataType;
BEGIN
    SET @MyCursor = CURSOR FOR
    select top 1000 YourField from dbo.table
        where StatusID = 7      

    OPEN @MyCursor 
    FETCH NEXT FROM @MyCursor 
    INTO @MyField

    WHILE @@FETCH_STATUS = 0
    BEGIN
      /*
         YOUR ALGORITHM GOES HERE   
      */
      FETCH NEXT FROM @MyCursor 
      INTO @MyField 
    END; 

    CLOSE @MyCursor ;
    DEALLOCATE @MyCursor;
END;
FloChanz
la source
5
La bonne chose est de réécrire le processus pour qu'il n'ait pas besoin de boucler. Le bouclage est un très mauvais choix dans une base de données.
HLGEM
23
Peut-être avez-vous raison, mais avec les informations données dans la question au moment où j'ai écrit la réponse, l'utilisateur souhaite simplement parcourir un ensemble de données ... et un curseur est un moyen de le faire.
FloChanz
16
Les curseurs ne sont qu'un outil - il n'y a généralement rien de bien ou de mal à leur sujet. Observez la performance et décidez. Cette réponse (curseurs) est un choix possible. Vous pouvez également utiliser un WHILE LOOP, CTE, etc.
Chaînes
2
@FrenkyB Oui, vous pouvez. Regardez par ici ... stackoverflow.com/questions/11035187/…
sam yi
2
Félicitations, votre solution est même sur msdn: msdn.microsoft.com/en-us/library/ ... et j'aime vraiment la façon dont vous utilisez le type de données de champ.
Pete
111

C'est ce que j'ai fait si vous avez besoin de faire quelque chose d'itératif ... mais il serait sage de rechercher d'abord les opérations d'ensemble.

select top 1000 TableID
into #ControlTable 
from dbo.table
where StatusID = 7

declare @TableID int

while exists (select * from #ControlTable)
begin

    select top 1 @TableID = TableID
    from #ControlTable
    order by TableID asc

    -- Do something with your TableID

    delete #ControlTable
    where TableID = @TableID

end

drop table #ControlTable
sam yi
la source
4
L'utilisation d'un CURSEUR (voir la réponse ci-dessous) semble être une solution beaucoup plus élégante.
Mikhail Glukhov
Pourquoi cette réponse a-t-elle plus de votes positifs que la solution de curseur?
ataravati
29
@ataravati Parce que cette solution lit plus proprement pour de nombreux programmeurs que les curseurs. La syntaxe des curseurs est plutôt gênante pour certains.
Brian Webster
Je vous remercie! Mon exemple avec mise à jour et regroupement par logique en utilisant le code ci-dessus: pastebin.com/GAjUNNi9 . Peut-être sera-t-il utile à n'importe qui.
Nigrimmist
la variable peut-elle être utilisée comme nom de colonne dans l'instruction de mise à jour à l'intérieur de la boucle? Quelque chose comme "Update TableName SET @ ColumnName = 2"
MH
28

Petit changement dans la réponse de sam yi (pour une meilleure lisibilité):

select top 1000 TableID
into #ControlTable 
from dbo.table
where StatusID = 7

declare @TableID int

while exists (select * from #ControlTable)
begin

    select @TableID = (select top 1 TableID
                       from #ControlTable
                       order by TableID asc)

    -- Do something with your TableID

    delete #ControlTable
    where TableID = @TableID

end

drop table #ControlTable
Précepte
la source
1
@bluish, cette réponse corrige la réponse de sam yi. Cette correction est principalement à l'intérieur de l' select @TableID = (...)instruction.
Simple Sandman
Je pense que cette réponse doit être choisie de cette question
sajadre
14

En utilisant le curseur, vous pouvez facilement parcourir les enregistrements individuellement et imprimer les enregistrements séparément ou sous la forme d'un message unique comprenant tous les enregistrements.

DECLARE @CustomerID as INT;
declare @msg varchar(max)
DECLARE @BusinessCursor as CURSOR;

SET @BusinessCursor = CURSOR FOR
SELECT CustomerID FROM Customer WHERE CustomerID IN ('3908745','3911122','3911128','3911421')

OPEN @BusinessCursor;
    FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
    WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @msg = '{
              "CustomerID": "'+CONVERT(varchar(10), @CustomerID)+'",
              "Customer": {
                "LastName": "LastName-'+CONVERT(varchar(10), @CustomerID) +'",
                "FirstName": "FirstName-'+CONVERT(varchar(10), @CustomerID)+'",    
              }
            }|'
        print @msg
    FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
END
Agnel Amodia
la source
1
cela semble intéressant. Je me demande ce que signifie l'identifiant @.
netskink
@ est juste pour différencier en tant que variables.
Agnel Amodia
9

Juste une autre approche si vous utilisez bien les tables temporaires.J'ai personnellement testé cela et cela ne causera aucune exception (même si la table temporaire ne contient aucune donnée).

CREATE TABLE #TempTable
(
    ROWID int identity(1,1) primary key,
    HIERARCHY_ID_TO_UPDATE int,
)

--create some testing data
--INSERT INTO #TempTable VALUES(1)
--INSERT INTO #TempTable VALUES(2)
--INSERT INTO #TempTable VALUES(4)
--INSERT INTO #TempTable VALUES(6)
--INSERT INTO #TempTable VALUES(8)

DECLARE @MAXID INT, @Counter INT

SET @COUNTER = 1
SELECT @MAXID = COUNT(*) FROM #TempTable

WHILE (@COUNTER <= @MAXID)
BEGIN
    --DO THE PROCESSING HERE 
    SELECT @HIERARCHY_ID_TO_UPDATE = PT.HIERARCHY_ID_TO_UPDATE
    FROM #TempTable AS PT
    WHERE ROWID = @COUNTER

    SET @COUNTER = @COUNTER + 1
END


IF (OBJECT_ID('tempdb..#TempTable') IS NOT NULL)
BEGIN
    DROP TABLE #TempTable
END
Sandeep
la source
C'est vraiment bizarre. Il contient beaucoup d'erreurs, utiliser également deux variables où l'on va de 1 à COUNT(*)et la seconde de COUNT(*)à 1 est bizarre.
David Ferenczy Rogožan
La variable MAXID est utilisée pour boucler. La variable COUNTER est utilisée pour effectuer une opération sur un enregistrement particulier de la table. Si je lis la question, elle parle de "avoir quelques enregistrements que je souhaite parcourir et faire quelque chose avec chaque enregistrement". Je me trompe peut-être, mais veuillez indiquer ce qui ne va pas ci-dessus @DAWID
Sandeep
2
Je pense que la manière dont vous utilisez ces variables dans votre code est évidente. Vous pouvez simplement avoir WHILE (@COUTNER <= @ROWID)et vous n'avez pas besoin de décrémenter @ROWIDà chaque itération. BTW que se passe-t-il si les ROWIDs de votre table ne sont pas continus (certaines lignes ont été précédemment supprimées).
David Ferenczy Rogožan
1
Quand suggéreriez-vous d'utiliser une table de températures plutôt qu'un curseur? S'agit-il simplement d'un choix de conception ou at-il de meilleures performances?
h0r53 le
4

Vous pouvez choisir de classer vos données et d'ajouter un ROW_NUMBER et un compte à rebours jusqu'à zéro pendant l'itération de votre ensemble de données.

-- Get your dataset and rank your dataset by adding a new row_number
SELECT  TOP 1000 A.*, ROW_NUMBER() OVER(ORDER BY A.ID DESC) AS ROW
INTO #TEMPTABLE 
FROM DBO.TABLE AS A
WHERE STATUSID = 7;

--Find the highest number to start with
DECLARE @COUNTER INT = (SELECT MAX(ROW) FROM #TEMPTABLE);
DECLARE @ROW INT;

-- Loop true your data until you hit 0
WHILE (@COUNTER != 0)
BEGIN

    SELECT @ROW = ROW
    FROM #TEMPTABLE
    WHERE ROW = @COUNTER
    ORDER BY ROW DESC

    --DO SOMTHING COOL  

    -- SET your counter to -1
    SET @COUNTER = @ROW -1
END

DROP TABLE #TEMPTABLE
Bunkerbuster
la source
2

de cette façon, nous pouvons itérer dans les données de table.

DECLARE @_MinJobID INT
DECLARE @_MaxJobID INT
CREATE  TABLE #Temp (JobID INT)

INSERT INTO #Temp SELECT * FROM DBO.STRINGTOTABLE(@JobID,',')
SELECT @_MinJID = MIN(JobID),@_MaxJID = MAX(JobID)  FROM #Temp

    WHILE @_MinJID <= @_MaxJID
    BEGIN

        INSERT INTO Mytable        
        (        
            JobID,        
        )        

        VALUES        
        (        
            @_MinJobID,        
        ) 

        SET @_MinJID = @_MinJID + 1;
    END

DROP TABLE #Temp

STRINGTOTABLE est une fonction définie par l'utilisateur qui analysera les données séparées par des virgules et retournera la table. Merci

Monojit Sarkar
la source
1

Je pense que c'est l'exemple du moyen le plus simple d'itérer un élément.

declare @cateid int
select CateID into [#TempTable] from Category where GroupID = 'STOCKLIST'

while (select count(*) from #TempTable) > 0
begin
    select top 1 @cateid = CateID from #TempTable
    print(@cateid)

    --DO SOMETHING HERE

    delete #TempTable where CateID = @cateid
end

drop table #TempTable
江明哲
la source