2009-10-16 4 views
56

Есть ли все равно, чтобы перебрать переменную таблицы в T-SQL?Могу ли я пропустить переменную таблицы в T-SQL?

DECLARE @table1 TABLE (col1 int) 
INSERT into @table1 SELECT col1 FROM table2 

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

DECLARE cursor1 CURSOR 
    FOR SELECT col1 FROM table2 
OPEN cursor1 
FETCH NEXT FROM cursor1 

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

Любая помощь очень ценится.

+0

Аналогичный вопрос здесь: http://stackoverflow.com/questions/61967/is-there-a-way-to-loop-through-a-table-variable-in-tsql-without-using-a- курсор – demp

+2

"Курсоры кажутся менее гибкими, чем переменные таблицы". Это утверждение не имеет смысла. Это совершенно разные вещи. Вы можете, конечно, использовать курсор для итерации через переменную таблицы. –

ответ

79

Добавить идентичность в переменную таблицы, и сделать простой цикл от 1 до @@ ROWCOUNT вставного-SELECT.

Попробуйте это:

DECLARE @RowsToProcess int 
DECLARE @CurrentRow  int 
DECLARE @SelectCol1  int 

DECLARE @table1 TABLE (RowID int not null primary key identity(1,1), col1 int) 
INSERT into @table1 (col1) SELECT col1 FROM table2 
SET @[email protected]@ROWCOUNT 

SET @CurrentRow=0 
WHILE @CurrentRow<@RowsToProcess 
BEGIN 
    SET @[email protected]+1 
    SELECT 
     @SelectCol1=col1 
     FROM @table1 
     WHERE [email protected] 

    --do your thing here-- 

END 
+4

Это похоже на самый простой из множества. Благодаря! – Kuyenda

12
DECLARE @table1 TABLE (
    idx int identity(1,1), 
    col1 int) 

DECLARE @counter int 

SET @counter = 1 

WHILE(@counter < SELECT MAX(idx) FROM @table1) 
BEGIN 
    DECLARE @colVar INT 

    SELECT @colVar = col1 FROM @table1 WHERE idx = @counter 

    -- Do your work here 

    SET @counter = @counter + 1 
END 

Верьте или нет, это на самом деле более эффективно и действенно, чем с помощью курсора.

+0

Зачем выбирать максимальный каждый раз в цикле? –

+0

Вы можете выбрать его один раз и сохранить его в переменной достаточно легко ... это было всего на несколько нажатий клавиш короче. –

+1

Почему каждый раз в цикле выбирается максимальное значение? В результате вы должны дважды нажать на переменную таблицы на итерацию. Вы можете удалить SELECT MAX() в WHILE(), если вы захватите @@ ROWCOUNT из таблицы varaible population, как и в моем ответе. –

6

Вы можете прокручивать переменную таблицы или перемещаться по ней. Это то, что мы обычно называем RBAR - произносится Reebar и означает Row-By-Agonizing-Row.

Я бы предложил найти ответ SET-BASED на ваш вопрос (мы можем помочь с этим) и как можно больше удалиться от rbars.

+0

Именно поэтому я хочу использовать переменную таблицы вместо курсора. Обычно я ищу способ получить свой предполагаемый результат, используя JOIN для переменной таблицы, но если я не могу найти способ использовать JOIN, я могу вернуться к циклу на той же переменной таблицы. Но я согласен, что лучше всего использовать set-based. – Kuyenda

+0

Цитирование по переменной таблицы не лучше, чем курсор. Фактически, это может быть действительно хуже. Единственное реальное преимущество изменения кода от курсоров до циклов - «права бахвальства». Пример: «У меня нет курсоров в моем коде». –

2

Вот еще один ответ, похожий на Джастина, но ему не нужна личность или совокупность, а только первичный (уникальный) ключ.

declare @table1 table(dataKey int, dataCol1 varchar(20), dataCol2 datetime) 
declare @dataKey int 
while exists select 'x' from @table1 
begin 
    select top 1 @dataKey = dataKey 
    from @table1 
    order by /*whatever you want:*/ dataCol2 desc 

    -- do processing 

    delete from @table1 where dataKey = @dataKey 
end 
+0

каждой итерации вы попадаете в переменную таблицы 3 раза, что не может быть таким эффективным –

2

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

DECLARE 
    @LoopId int 
,@MyData varchar(100) 

DECLARE @CheckThese TABLE 
(
    LoopId int not null identity(1,1) 
    ,MyData varchar(100) not null 
) 


INSERT @CheckThese (YourData) 
select MyData from MyTable 
order by DoesItMatter 

SET @LoopId = @@rowcount 

WHILE @LoopId > 0 
BEGIN 
    SELECT @MyData = MyData 
    from @CheckThese 
    where LoopId = @LoopId 

    -- Do whatever 

    SET @LoopId = @LoopId - 1 
END 

Точка Raj More важна только для выполнения циклов, если вам нужно.

2

Я не знал о структуре WHILE.

Структура WHILE с переменной таблицы, однако, похожа на использование CURSOR, поскольку вам все же нужно выбрать строку в переменной на основе строки IDENTITY, которая фактически является FETCH.

Есть ли разница между использованием WHERE и чем-то вроде следующего?

DECLARE @table1 TABLE (col1 int) 
INSERT into @table1 SELECT col1 FROM table2 

DECLARE cursor1 CURSOR 
    FOR @table1 
OPEN cursor1 
FETCH NEXT FROM cursor1 

Я не знаю, возможно ли это. Я полагаю, вам, возможно, придется это сделать:

DECLARE cursor1 CURSOR 
    FOR SELECT col1 FROM @table1 
OPEN cursor1 
FETCH NEXT FROM cursor1 

Спасибо вам за помощь!

+1

Ваш код: _DECLARE cursor1 CURSOR FOR @ table1 OPEN cursor1_ не будет работать. Курсоры должны иметь SELECT в своем определении, например, ваш второй пример кода. Если вы выполните некоторые тесты, вы обнаружите, что цикл без использования курсора быстрее, чем цикл с помощью курсора. –

1

Вот моя версия того же решения ...

declare @id int 

     SELECT @id = min(fPat.PatientID) 
     FROM tbPatients fPat 
     WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0) 

while @id is not null 
begin 
    SELECT fPat.PatientID, fPat.InsNotes 
    FROM tbPatients fPat 
    WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0) AND [email protected] 

    SELECT @id = min(fPat.PatientID) 
    FROM tbPatients fPat 
    WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0)AND fPat.PatientID>@id 

end 
5

выглядеть следующим образом демо:

DECLARE @vTable TABLE (IdRow int not null primary key identity(1,1),ValueRow int); 

-------Initialize--------- 
insert into @vTable select 345; 
insert into @vTable select 795; 
insert into @vTable select 565; 
--------------------------- 

DECLARE @cnt int = 1; 
DECLARE @max int = (SELECT MAX(IdRow) FROM @vTable); 

WHILE @cnt <= @max 
BEGIN 
    DECLARE @tempValueRow int = (Select ValueRow FROM @vTable WHERE IdRow = @cnt); 

    ---work demo---- 
    print '@tempValueRow:' + convert(varchar(10),@tempValueRow); 
    print '@cnt:' + convert(varchar(10),@cnt); 
    print''; 
    -------------- 

    set @cnt = @cnt+1; 
END 

Версия без idRow, используя ROW_NUMBER

DECLARE @vTable TABLE (ValueRow int); 
-------Initialize--------- 
insert into @vTable select 345; 
insert into @vTable select 795; 
insert into @vTable select 565; 
--------------------------- 

DECLARE @cnt int = 1; 
DECLARE @max int = (select count(*) from @vTable); 

WHILE @cnt <= @max 
BEGIN 
    DECLARE @tempValueRow int = (
     select ValueRow 
     from (select ValueRow 
      , ROW_NUMBER() OVER(ORDER BY (select 1)) as RowId 
      from @vTable 
     ) T1 
    where t1.RowId = @cnt 
    ); 

    ---work demo---- 
    print '@tempValueRow:' + convert(varchar(10),@tempValueRow); 
    print '@cnt:' + convert(varchar(10),@cnt); 
    print''; 
    -------------- 

    set @cnt = @cnt+1; 
END 
5

Мои два цента .. От ответа KM-х, если вы хотите отказаться от одного. переменной, вы можете делать обратный отсчет на @RowsToProcess вместо подсчета.

DECLARE @RowsToProcess int; 

DECLARE @table1 TABLE (RowID int not null primary key identity(1,1), col1 int) 
INSERT into @table1 (col1) SELECT col1 FROM table2 
SET @RowsToProcess = @@ROWCOUNT 

WHILE @RowsToProcess > 0 -- Countdown 
BEGIN 
    SELECT * 
     FROM @table1 
     WHERE [email protected] 

    --do your thing here-- 

    SET @RowsToProcess = @RowsToProcess - 1; -- Countdown 
END 
+0

Это лучшее решение в качестве принятого ответа, так как оно не зависит от содержимого переменной таблицы. – beerwin

0

Следующий цикл хранимой процедуры через переменную таблицы и печатает ее в порядке возрастания. В этом примере используется WHILE LOOP.

CREATE PROCEDURE PrintSequenceSeries 
    -- Add the parameters for the stored procedure here 
    @ComaSeperatedSequenceSeries nVarchar(MAX) 
AS 
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements. 
    SET NOCOUNT ON; 

    DECLARE @SERIES_COUNT AS INTEGER 
    SELECT @SERIES_COUNT = COUNT(*) FROM PARSE_COMMA_DELIMITED_INTEGER(@ComaSeperatedSequenceSeries, ',') --- ORDER BY ITEM DESC 

    DECLARE @CURR_COUNT AS INTEGER 
    SET @CURR_COUNT = 1 

    DECLARE @SQL AS NVARCHAR(MAX) 

    WHILE @CURR_COUNT <= @SERIES_COUNT 
    BEGIN 
     SET @SQL = 'SELECT TOP 1 T.* FROM ' + 
      '(SELECT TOP ' + CONVERT(VARCHAR(20), @CURR_COUNT) + ' * FROM PARSE_COMMA_DELIMITED_INTEGER(''' + @ComaSeperatedSequenceSeries + ''' , '','') ORDER BY ITEM ASC) AS T ' + 
      'ORDER BY T.ITEM DESC ' 
     PRINT @SQL 
     EXEC SP_EXECUTESQL @SQL 
     SET @CURR_COUNT = @CURR_COUNT + 1 
    END; 

следующее заявление Выполнение хранимой процедуры:

EXEC PrintSequenceSeries '11,2,33,14,5,60,17,98,9,10' 

Результат отображается в окне SQL-запроса показан ниже:

The Result of PrintSequenceSeries

Функция PARSE_COMMA_DELIMITED_INTEGER(), которая возвращает переменную TABLE как показано ниже:

CREATE FUNCTION [dbo].[parse_comma_delimited_integer] 
     (
      @LIST  VARCHAR(8000), 
      @DELIMITER VARCHAR(10) = ', 
      ' 
     ) 

     -- TABLE VARIABLE THAT WILL CONTAIN VALUES 
     RETURNS @TABLEVALUES TABLE 
     (
      ITEM INT 
     ) 
     AS 
     BEGIN 
      DECLARE @ITEM VARCHAR(255) 

      /* LOOP OVER THE COMMADELIMITED LIST */ 
      WHILE (DATALENGTH(@LIST) > 0) 
       BEGIN 
        IF CHARINDEX(@DELIMITER,@LIST) > 0 
         BEGIN 
          SELECT @ITEM = SUBSTRING(@LIST,1,(CHARINDEX(@DELIMITER, @LIST)-1)) 
          SELECT @LIST = SUBSTRING(@LIST,(CHARINDEX(@DELIMITER, @LIST) + 
          DATALENGTH(@DELIMITER)),DATALENGTH(@LIST)) 
         END 
        ELSE 
         BEGIN 
          SELECT @ITEM = @LIST 
          SELECT @LIST = NULL 
         END 

        -- INSERT EACH ITEM INTO TEMP TABLE 
        INSERT @TABLEVALUES 
        (
         ITEM 
        ) 
        SELECT ITEM = CONVERT(INT, @ITEM) 
       END 
     RETURN 
     END