Есть ли способ перебрать табличную переменную в TSQL без использования курсора? - PullRequest
219 голосов
/ 15 сентября 2008

Допустим, у меня есть следующая простая табличная переменная:

declare @databases table
(
    DatabaseID    int,
    Name        varchar(15),   
    Server      varchar(15)
)
-- insert a bunch rows into @databases

Является ли объявление и использование курсора моим единственным вариантом, если я хочу перебирать строки? Есть ли другой способ?

Ответы [ 21 ]

330 голосов
/ 15 сентября 2008

Прежде всего, вы должны быть абсолютно уверены, что вам нужно перебирать каждую строку - операции на основе набора будут выполняться быстрее в каждом случае, о котором я могу подумать, и обычно будут использовать более простой код.

В зависимости от ваших данных может быть возможно выполнить цикл, просто используя операторы выбора, как показано ниже:

Declare @Id int

While (Select Count(*) From ATable Where Processed = 0) > 0
Begin
    Select Top 1 @Id = Id From ATable Where Processed = 0

    --Do some processing here

    Update ATable Set Processed = 1 Where Id = @Id 

End

Другая альтернатива - использовать временную таблицу:

Select *
Into   #Temp
From   ATable

Declare @Id int

While (Select Count(*) From #Temp) > 0
Begin

    Select Top 1 @Id = Id From #Temp

    --Do some processing here

    Delete #Temp Where Id = @Id

End

Выбор, который вы должны выбрать, действительно зависит от структуры и объема ваших данных.

Примечание: Если вы используете SQL Server, вам лучше использовать:

WHILE EXISTS(SELECT * FROM #Temp)

Использование COUNT должно касаться каждой строки в таблице, EXISTS - только первой (см. ответ Иосифа ниже).

124 голосов
/ 15 сентября 2008

Просто быстрое примечание, если вы используете SQL Server (2008 и выше), примеры, которые имеют:

While (Select Count(*) From #Temp) > 0

Лучше бы подали с

While EXISTS(SELECT * From #Temp)

Счетчик должен коснуться каждой строки в таблице, EXISTS - только первой.

36 голосов
/ 18 января 2010

Вот как я это делаю:

declare @RowNum int, @CustId nchar(5), @Name1 nchar(25)

select @CustId=MAX(USERID) FROM UserIDs     --start with the highest ID
Select @RowNum = Count(*) From UserIDs      --get total number of records
WHILE @RowNum > 0                          --loop until no more records
BEGIN   
    select @Name1 = username1 from UserIDs where USERID= @CustID    --get other info from that row
    print cast(@RowNum as char(12)) + ' ' + @CustId + ' ' + @Name1  --do whatever

    select top 1 @CustId=USERID from UserIDs where USERID < @CustID order by USERID desc--get the next one
    set @RowNum = @RowNum - 1                               --decrease count
END

Без курсоров, без временных таблиц, без дополнительных столбцов. Столбец USERID должен быть уникальным целым числом, как и большинство первичных ключей.

20 голосов
/ 17 сентября 2008

Определите свою временную таблицу следующим образом -

declare @databases table
(
    RowID int not null identity(1,1) primary key,
    DatabaseID    int,
    Name        varchar(15),   
    Server      varchar(15)
)

-- insert a bunch rows into @databases

Тогда сделай это -

declare @i int
select @i = min(RowID) from @databases
declare @max int
select @max = max(RowID) from @databases

while @i <= @max begin
    select DatabaseID, Name, Server from @database where RowID = @i --do some stuff
    set @i = @i + 1
end
14 голосов
/ 15 сентября 2008

Вот как бы я это сделал:

Select Identity(int, 1,1) AS PK, DatabaseID
Into   #T
From   @databases

Declare @maxPK int;Select @maxPK = MAX(PK) From #T
Declare @pk int;Set @pk = 1

While @pk <= @maxPK
Begin

    -- Get one record
    Select DatabaseID, Name, Server
    From @databases
    Where DatabaseID = (Select DatabaseID From #T Where PK = @pk)

    --Do some processing here
    -- 

    Select @pk = @pk + 1
End

[Редактировать] Поскольку я, вероятно, пропустил слово "переменная", когда впервые прочитал вопрос, вот обновленный ответ ...


declare @databases table
(
    PK            int IDENTITY(1,1), 
    DatabaseID    int,
    Name        varchar(15),   
    Server      varchar(15)
)
-- insert a bunch rows into @databases
--/*
INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MainDB', 'MyServer'
INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MyDB',   'MyServer2'
--*/

Declare @maxPK int;Select @maxPK = MAX(PK) From @databases
Declare @pk int;Set @pk = 1

While @pk <= @maxPK
Begin

    /* Get one record (you can read the values into some variables) */
    Select DatabaseID, Name, Server
    From @databases
    Where PK = @pk

    /* Do some processing here */
    /* ... */ 

    Select @pk = @pk + 1
End
9 голосов
/ 17 сентября 2008

Если у вас нет выбора, кроме как идти строка за строкой, создавая курсор FAST_FORWARD. Это будет так же быстро, как создание цикла while, и его будет намного легче поддерживать в течение длительного времени.

FAST_FORWARD Указывает курсор FORWARD_ONLY, READ_ONLY с включенной оптимизацией производительности. FAST_FORWARD не может быть указан, если также указаны SCROLL или FOR_UPDATE.

4 голосов
/ 18 июля 2013

Другой подход без необходимости изменения схемы или использования временных таблиц:

DECLARE @rowCount int = 0
  ,@currentRow int = 1
  ,@databaseID int
  ,@name varchar(15)
  ,@server varchar(15);

SELECT @rowCount = COUNT(*)
FROM @databases;

WHILE (@currentRow <= @rowCount)
BEGIN
  SELECT TOP 1
     @databaseID = rt.[DatabaseID]
    ,@name = rt.[Name]
    ,@server = rt.[Server]
  FROM (
    SELECT ROW_NUMBER() OVER (
        ORDER BY t.[DatabaseID], t.[Name], t.[Server]
       ) AS [RowNumber]
      ,t.[DatabaseID]
      ,t.[Name]
      ,t.[Server]
    FROM @databases t
  ) rt
  WHERE rt.[RowNumber] = @currentRow;

  EXEC [your_stored_procedure] @databaseID, @name, @server;

  SET @currentRow = @currentRow + 1;
END
3 голосов
/ 29 апреля 2012
-- [PO_RollBackOnReject]  'FININV10532'
alter procedure PO_RollBackOnReject
@CaseID nvarchar(100)

AS
Begin
SELECT  *
INTO    #tmpTable
FROM   PO_InvoiceItems where CaseID = @CaseID

Declare @Id int
Declare @PO_No int
Declare @Current_Balance Money


While (Select ROW_NUMBER() OVER(ORDER BY PO_LineNo DESC) From #tmpTable) > 0
Begin
        Select Top 1 @Id = PO_LineNo, @Current_Balance = Current_Balance,
        @PO_No = PO_No
        From #Temp
        update PO_Details
        Set  Current_Balance = Current_Balance + @Current_Balance,
            Previous_App_Amount= Previous_App_Amount + @Current_Balance,
            Is_Processed = 0
        Where PO_LineNumber = @Id
        AND PO_No = @PO_No
        update PO_InvoiceItems
        Set IsVisible = 0,
        Is_Processed= 0
        ,Is_InProgress = 0 , 
        Is_Active = 0
        Where PO_LineNo = @Id
        AND PO_No = @PO_No
End
End
3 голосов
/ 15 сентября 2008

Вы можете использовать цикл while:

While (Select Count(*) From #TempTable) > 0
Begin
    Insert Into @Databases...

    Delete From #TempTable Where x = x
End
3 голосов
/ 04 мая 2016

Легкий, без необходимости создавать дополнительные таблицы, если у вас есть целое число ID на столе

Declare @id int = 0, @anything nvarchar(max)
WHILE(1=1) BEGIN
  Select Top 1 @anything=[Anything],@id=@id+1 FROM Table WHERE ID>@id
  if(@@ROWCOUNT=0) break;

  --Process @anything

END
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...