SQL Server-Schleife - Wie durchlaufe ich eine Reihe von Datensätzen?

151

Wie durchlaufe ich eine Reihe von Datensätzen aus einer Auswahl?

Nehmen wir zum Beispiel an, ich habe ein paar Datensätze, die ich durchlaufen und mit jedem Datensatz etwas anfangen möchte. Hier ist eine primitive Version meiner Auswahl:

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

Vielen Dank

Funky
quelle
5
Was möchten Sie mit jedem Datensatz tun? Am liebsten würden Sie die Arbeit in einer SQL-Abfrage erledigen. Es sei denn, Sie müssten T-SQL verwenden, möglicherweise mit Cursorn.
Gordon Linoff
2
Ich würde einen Cursor verwenden.
FloChanz
5
Das wird ziemlich langsam sein - ist es nicht möglich, den gespeicherten Prozess neu zu schreiben oder einen Teil der Logik daraus zu entfernen, um satzbasiert zu arbeiten?
Brücke
2
@Funky was macht der sproc? Oft kann Code satzbasiert neu geschrieben werden (dh Schleifen vermeiden). Wenn Sie fest davon überzeugt sind, dass Sie eine RBAR-Operation ausführen möchten ( simple-talk.com/sql/t-sql-programming/… ), ist ein Cursor das, was Sie untersuchen möchten.
Gvee
1
Vielleicht können Sie genauer erklären, was Sie mit diesen Daten machen werden. In den meisten Fällen können Sie problemlos eine einzelne SQL-Abfrage schreiben, die in einer Aktion das erledigt, was Sie benötigen, anstatt einzelne Datensätze zu durchlaufen.
Alan Barber

Antworten:

212

Mit T-SQL und Cursorn wie folgt:

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
quelle
5
Das Richtige ist, den Prozess neu zu schreiben, damit er nicht wiederholt werden muss. Looping ist eine extrem schlechte Wahl in einer Datenbank.
HLGEM
23
Vielleicht haben Sie Recht, aber mit den Informationen in der Frage zu dem Zeitpunkt, als ich die Antwort geschrieben habe, möchte der Benutzer nur einen Datensatz durchlaufen ... und ein Cursor ist eine Möglichkeit, dies zu tun.
FloChanz
16
Cursor sind nur ein Werkzeug - nichts, was im Allgemeinen richtig oder falsch ist. Beobachten Sie die Leistung und entscheiden Sie. Diese Antwort (Cursor) ist eine mögliche Wahl. Sie können auch eine WHILE LOOP, CTE usw. verwenden
Ketten
2
@FrenkyB Ja, das kannst du. Schauen Sie so ... stackoverflow.com/questions/11035187/…
sam yi
2
Herzlichen Glückwunsch, Ihre Lösung ist sogar auf msdn: msdn.microsoft.com/en-us/library/… und ich mag es wirklich, wie Sie den Felddatentyp verwenden.
Pete
110

Dies ist, was ich getan habe, wenn Sie etwas iteratives tun müssen ... aber es wäre ratsam, zuerst nach festgelegten Operationen zu suchen.

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
quelle
4
Die Verwendung eines CURSOR (siehe Antwort unten) scheint eine viel elegantere Lösung zu sein.
Mikhail Glukhov
Warum hat diese Antwort mehr Up-Votes als die Cursor-Lösung?
Ataravati
28
@ataravati Weil diese Lösung für viele Programmierer sauberer liest als Cursor. Die Syntax für Cursor ist für einige eher umständlich.
Brian Webster
Danke dir! Mein Beispiel mit Aktualisierung und Gruppierung nach Logik unter Verwendung des obigen Codes: pastebin.com/GAjUNNi9 . Vielleicht wird es für jeden nützlich sein.
Nigrimmist
Kann die Variable als Spaltenname in der Update-Anweisung innerhalb der Schleife verwendet werden? So etwas wie "Update TableName SET @ ColumnName = 2"
MH
28

Kleine Änderung an Sam Yis Antwort (zur besseren Lesbarkeit):

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
Gebot
quelle
1
@bluish, diese Antwort korrigiert die Antwort von Sam Yi. Diese Korrektur ist hauptsächlich in der select @TableID = (...)Anweisung enthalten.
Einfacher Sandmann
Ich denke, diese Antwort muss aus dieser Frage ausgewählt werden
Sajadre
14

Mit dem Cursor können Sie Datensätze einfach einzeln durchlaufen und Datensätze separat oder als einzelne Nachricht mit allen Datensätzen drucken.

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
quelle
1
das sieht interessant aus. Ich frage mich, was der @ Bezeichner bedeutet.
Netskink
@ ist nur als Variablen zu differenzieren.
Agnel Amodia
9

Nur ein weiterer Ansatz, wenn Sie temporäre Tabellen verwenden können. Ich habe dies persönlich getestet und es wird keine Ausnahme verursachen (auch wenn temporäre Tabellen keine Daten enthalten.)

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
quelle
Das ist wirklich komisch. Es enthält viele Fehler, auch die Verwendung von zwei Variablen, bei denen eine von 1 nach COUNT(*)und die zweite von COUNT(*)1 geht , ist seltsam.
David Ferenczy Rogožan
Die Variable MAXID wird zum Durchlaufen verwendet. Die Variable COUNTER wird verwendet, um eine Operation für einen bestimmten Datensatz in der Tabelle auszuführen. Wenn ich die Frage lese, geht es um "habe ein paar Datensätze, die ich durchlaufen und mit jedem Datensatz etwas machen möchte". Ich kann mich irren, aber bitte weisen Sie darauf hin, was über @DAWID
Sandeep
2
Ich denke, es ist offensichtlich, wie Sie diese Variablen in Ihrem Code verwenden. Sie können einfach haben WHILE (@COUTNER <= @ROWID)und müssen nicht @ROWIDin jeder Iteration dekrementieren . Übrigens, was passiert, wenn ROWIDs in Ihrer Tabelle nicht fortlaufend sind (einige Zeilen wurden zuvor gelöscht).
David Ferenczy Rogožan
1
Wann würden Sie vorschlagen, eine temporäre Tabelle anstelle eines Cursors zu verwenden? Ist dies nur eine Designentscheidung oder hat man eine bessere Leistung?
h0r53
4

Sie können Ihre Daten ordnen und eine ROW_NUMBER hinzufügen und bis Null zählen, während Sie Ihren Datensatz iterieren.

-- 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
quelle
2

Auf diese Weise können wir in Tabellendaten iterieren.

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 ist eine benutzerdefinierte Funktion, die durch Kommas getrennte Daten analysiert und die Tabelle zurückgibt . Vielen Dank

Monojit Sarkar
quelle
1

Ich denke, dies ist der einfache Weg, um ein Element zu iterieren.

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
江明哲
quelle