Цикл SQL Server - як я проходжу цикл через набір записів


151

як я переглядаю набір записів із вибору?

Так скажіть, наприклад, у мене є кілька записів, які я хочу переглядати і робити щось із кожним записом. Ось примітивна версія мого вибору:

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

Дякую


5
Що ви хочете зробити для кожного запису? Перевагою було б виконати роботу в SQL-запиті. Якщо забороняється використовувати T-SQL, можливо, з курсорами.
Гордон Лінофф

2
Я б використав курсор.
FloChanz

5
Це буде досить повільно - хіба неможливо переписати збережену програму чи перенести якусь логіку з неї, щоб вона працювала заданим чином?
Міст

2
@Funky, що робить паросток? Часто код можна переписати в наборі (тобто уникати циклів). Якщо ви впевнені, що хочете виконати операцію RBAR ( simple-talk.com/sql/t-sql-programming/… ), курсор - це те, що ви хочете дослідити.
gvee

1
Можливо, ви можете пояснити, що ви будете робити з цими даними більш детально. У більшості випадків ви можете легко написати єдиний SQL-запит, який зробить те, що вам потрібно виконати за одну дію, а не прокручувати окремі записи.
Алан Барбер

Відповіді:


212

Використовуючи T-SQL і такі курсори:

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
Правильно - переписати процес, щоб він не потребував циклу. Цикл - це надзвичайно поганий вибір у базі даних.
HLGEM

23
Можливо, ти маєш рацію, але з інформацією, наданою у запитанні, коли я писав відповідь, користувач просто хоче провести цикл даних через набір даних ... і Курсор - це спосіб це зробити.
FloChanz

16
Курсори - це лише інструмент - взагалі нічого про них неправильного чи неправильного. Поспостерігайте за виконанням та приймайте рішення. Ця відповідь (курсори) - один із можливих варіантів. Ви також можете використовувати WHILE LOOP, CTE тощо.
Ланцюжки

2
@FrenkyB Так, можна. Подивіться ось так ... stackoverflow.com/questions/11035187/…
сам дохід

2
Вітаємо, ваше рішення є навіть у msdn : msdn.microsoft.com/en-us/library/…, і мені дуже подобається, як ви використовуєте Тип даних поля.
Піт

111

Це те, що я робив, якщо вам потрібно зробити щось ітераційне ... але було б розумно спочатку пошукати встановлені операції.

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
Використання CURSOR (див. Відповідь нижче) здається набагато більш елегантним рішенням.
Михайло Глухов

Чому ця відповідь має більше голосів, ніж рішення курсору?
ataravati

29
@ataravati Тому що це рішення читає більш чітко програмістам, ніж курсори. Синтаксис курсорів для деяких досить незручний.
Брайан Вебстер

Дякую! Мій приклад з оновленням та групуванням за логікою за допомогою вищевказаного коду: pastebin.com/GAjUNNi9 . Можливо, комусь буде корисно.
Nigrimmist

Чи може змінна використовуватися як назва стовпця в операторі оновлення всередині циклу? Щось на кшталт "Оновити SETName SET @ ColumnName = 2"
MH

28

Невелика зміна відповіді сам-сам (для кращої читабельності):

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, ця відповідь виправляє відповідь Сем Іі. Ця корекція в основному всередині select @TableID = (...)твердження.
Простий Сендман

Я думаю, що з цього питання треба вибрати відповідь
саджадре

14

За допомогою курсору ви можете легко повторювати записи окремо і друкувати записи окремо або як одне повідомлення, що включає всі записи.

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
це виглядає цікаво. Цікаво, що означає ідентифікатор @.
netskink

@ просто розмежувати як змінні.
Агнел Амодія

9

Ще один підхід, якщо ви добре використовуєте темп таблиці. Я особисто перевірив це, і він не спричинить жодного винятку (навіть якщо в таблиці темп немає даних.)

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

Це справді дивно. Він містить багато помилок, також дивним є використання двох змінних, коли одна переходить від 1 до COUNT(*)другої, а друга - від COUNT(*)1.
Девід Ференчі Рогожан

Змінна MAXID використовується для перегляду через. Змінна COUNTER використовується для виконання операції над певним записом у таблиці. Якщо я прочитаю запитання, про яке йдеться, "у мене є кілька записів, які я хочу переглядати і робити щось із кожним записом". Я можу помилятися, але, будь ласка, вкажіть, що не так вище @DAWID
Sandeep

2
Я думаю, очевидно, як ви використовуєте ці змінні у своєму коді. Ви можете просто мати, WHILE (@COUTNER <= @ROWID)і вам не потрібно декрементувати @ROWIDв кожній ітерації. BTW, що станеться, якщо ROWIDs у вашій таблиці не є суцільним (деякі рядки раніше були видалені).
Девід Ференці Рогожан

1
Коли ви запропонуєте використовувати таблицю темпів за допомогою курсору? Це просто вибір дизайну, чи хтось має кращі показники?
h0r53

4

Ви можете вибрати позицію своїх даних і додати ROW_NUMBER і відлічити нуль під час ітерації вашого набору даних.

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

таким чином ми можемо перетворити на дані таблиці.

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 - це функція визначення користувача, яка буде аналізувати розділені комами дані та таблицю повернення. Дякую


1

Я думаю, що це простий спосіб прикладу повторити предмет.

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.