Цикл SQL Server - как мне пройти через набор записей


151

Как мне пройти через набор записей из выбора?

Например, скажем, у меня есть несколько записей, которые я хочу просмотреть и сделать с каждой записью. Вот примитивная версия моего выбора:

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

Спасибо


5
Что вы хотите сделать с каждой записью? Предпочтение будет отдаваться работе в запросе SQL. За исключением того, что вам нужно будет использовать T-SQL, возможно, с курсорами.
Гордон Линофф

2
Я бы использовал курсор.
FloChanz

5
Это будет довольно медленно - не возможно ли переписать сохраненный процесс или переместить часть логики из него для работы на основе набора?
Мост

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

1
Возможно, вы сможете объяснить, что вы будете делать с этими данными более подробно. В большинстве случаев вы можете легко написать один SQL-запрос, который будет выполнять то, что вам нужно, за одно действие, а не проходить по отдельным записям.
Алан Барбер

Ответы:


213

Используя 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/...
SaM уг

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 (см. Ответ ниже) кажется гораздо более элегантным решением.
Михаил Глухов

Почему этот ответ имеет больше голосов, чем решение курсора?
Атаравати

29
@ataravati Потому что это решение читается более понятным для многих программистов, чем курсоры. Синтаксис для курсоров довольно неудобен для некоторых.
Брайан Уэбстер

Спасибо! Мой пример с обновлением и группировкой по логике с использованием приведенного выше кода: pastebin.com/GAjUNNi9 . Может быть кому-нибудь пригодится.
Нигриммист

переменную можно использовать как имя столбца в операторе обновления внутри цикла? Что-то вроде «Обновить имя таблицы 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
@ синий, этот ответ исправляет ответ Сам Йи. Это исправление в основном внутри select @TableID = (...)утверждения.
Простой песочник

Я думаю, что этот ответ должен быть выбран из этого вопроса
Sajadre

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 используется для LOOP до конца. Переменная COUNTER используется для выполнения операции с определенной записью в таблице. Если я читаю вопрос, в котором говорится о «есть несколько записей, которые я хотел бы просмотреть и сделать что-то с каждой записью». Возможно, я ошибаюсь, но, пожалуйста, укажите, что не так над @DAWID
Sandeep

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

1
Когда бы вы предложили использовать Temp Table вместо Cursor? Это просто выбор дизайна или у него лучшая производительность?
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.