Pętla programu SQL Server - jak przeglądać zestaw rekordów


151

jak przeglądać zestaw rekordów z zaznaczenia?

Powiedzmy na przykład, że mam kilka rekordów, które chciałbym przejrzeć i zrobić coś z każdym z nich. Oto prymitywna wersja mojego wyboru:

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

Dzięki


5
Co chcesz zrobić z każdym rekordem? Preferowane byłoby wykonanie pracy w zapytaniu SQL. Poza tym, że musiałbyś używać T-SQL, być może z kursorami.
Gordon Linoff

2
Użyłbym kursora.
FloChanz

5
Będzie to dość powolne - czy nie jest możliwe ponowne napisanie przechowywanego procesu lub przeniesienie części logiki z niego, aby działała w sposób oparty na zestawie?
Most

2
@Funky, co robi sproc? Często kod można przepisać ponownie w sposób oparty na zestawie (tj. Unikać pętli). Jeśli jesteś nieugięty, że chcesz wykonać operację RBAR ( simple-talk.com/sql/t-sql-programming/ ), to kursor jest tym, co chcesz zbadać.
gvee

1
Być może możesz bardziej szczegółowo wyjaśnić, co będziesz robić z tymi danymi. W większości przypadków można łatwo napisać pojedyncze zapytanie SQL, które wykona to, co trzeba, w jednej akcji, zamiast przeglądania pojedynczych rekordów.
Alan Barber

Odpowiedzi:


212

Używając T-SQL i takich kursorów:

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;

5
Właściwe jest przepisanie procesu tak, aby nie musiał się zapętlać. Pętle to wyjątkowo zły wybór w bazie danych.
HLGEM

23
Być może masz rację, ale z informacjami podanymi w pytaniu w czasie, gdy pisałem odpowiedź, użytkownik chce po prostu przejrzeć zestaw danych ... a Cursor jest sposobem na to.
FloChanz,

16
Kursory to tylko narzędzie - ogólnie nie ma w nich nic dobrego ani złego. Obserwuj występ i zdecyduj. Ta odpowiedź (kursory) jest jednym z możliwych wyborów. Możesz także użyć WHILE LOOP, CTE itp.
Chains,

2
@FrenkyB Tak, możesz. Spójrz w ten sposób ... stackoverflow.com/questions/11035187/
sam yi

2
Gratulacje, Twoje rozwiązanie znajduje się nawet na msdn: msdn.microsoft.com/en-us/library/ ... i naprawdę podoba mi się sposób, w jaki używasz typu danych pola.
Pete

111

To właśnie robiłem, jeśli musisz zrobić coś iteracyjnego ... ale rozsądnie byłoby najpierw poszukać operacji na zbiorach.

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

4
Użycie KURSORA (patrz odpowiedź poniżej) wydaje się być dużo bardziej eleganckim rozwiązaniem.
Mikhail Glukhov

Dlaczego ta odpowiedź ma więcej głosów pozytywnych niż rozwiązanie kursora?
ataravati

29
@ataravati Ponieważ to rozwiązanie jest czytelniejsze dla wielu programistów niż kursory. Dla niektórych składnia kursorów jest raczej niezręczna.
Brian Webster,

Dziękuję Ci! Mój przykład z aktualizacją i grupowaniem według logiki przy użyciu powyższego kodu: pastebin.com/GAjUNNi9 . Może przyda się każdemu.
Nigrimmist

czy zmienna może być używana jako nazwa kolumny w instrukcji update wewnątrz pętli? Coś w rodzaju „Update TableName SET @ ColumnName = 2”
MH

28

Mała zmiana w odpowiedzi Sam Yi (dla lepszej czytelności):

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

1
@bluish, ta odpowiedź jest poprawą odpowiedzi Sam Yi. Ta korekta znajduje się głównie w select @TableID = (...)oświadczeniu.
Simple Sandman

Myślę, że ta odpowiedź musi być wybrana na to pytanie
sajadre

14

Używając kursora, można łatwo przechodzić przez poszczególne rekordy i drukować je oddzielnie lub jako pojedynczą wiadomość zawierającą wszystkie rekordy.

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

1
wygląda to interesująco. Zastanawiam się, co oznacza identyfikator @.
netskink

@ służy tylko do różnicowania jako zmienne.
Agnel Amodia

9

Po prostu inne podejście, jeśli możesz dobrze używać tabel tymczasowych. Osobiście to przetestowałem i nie spowoduje to żadnego wyjątku (nawet jeśli tabela tymczasowa nie zawiera żadnych danych).

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

To jest naprawdę dziwne. Zawiera wiele błędów, również użycie dwóch zmiennych, w których jedna przechodzi od 1 do, COUNT(*)a druga od COUNT(*)do 1, jest dziwne.
David Ferenczy Rogožan

Zmienna MAXID służy do wykonywania pętli. Zmienna COUNTER służy do wykonywania operacji na określonym rekordzie w tabeli. Jeśli przeczytam pytanie, o którym mowa, „mam kilka rekordów, które chciałbym przejrzeć i zrobić coś z każdym z nich”. Może się mylę, ale proszę wskazać, co jest nie tak powyżej @DAWID
Sandeep

2
Myślę, że to oczywiste, jak używasz tych zmiennych w swoim kodzie. Możesz po prostu mieć WHILE (@COUTNER <= @ROWID)i nie musisz zmniejszać @ROWIDw każdej iteracji. BTW, co się stanie, jeśli ROWIDs w Twojej tabeli nie są ciągłe (niektóre wiersze zostały wcześniej usunięte).
David Ferenczy Rogožan

1
Kiedy sugerowałbyś użycie tabeli Temp zamiast używania kursora? Czy to tylko wybór projektu, czy też ma się lepszą wydajność?
h0r53

4

Możesz uszeregować swoje dane, dodać ROW_NUMBER i odliczać do zera podczas iteracji zbioru danych.

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

2

w ten sposób możemy iterować dane w tabeli.

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 to funkcja definiowana przez użytkownika, która analizuje dane oddzielone przecinkami i zwraca tabelę. dzięki


1

Myślę, że jest to łatwy przykład iteracji elementu.

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.