2008-09-15 34 views
186

比方說,我有以下簡單的表變量:有沒有辦法在不使用遊標的情況下循環訪問TSQL中的表變量?

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

是聲明和使用遊標我唯一的選擇,如果我想通過行迭代?有另一種方法嗎?

+3

你能提供給我們的理由,你爲什麼要遍歷行,其他的解決方案,不需要迭代可能存在(並且在大多數情況下哪個速度快很多) – 2008-09-15 11:21:28

+0

同意pop ...可能不需要根據情況的光標。但是使用遊標沒有問題,如果你需要 – Shawn 2008-10-29 23:09:30

+0

http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them – HLGEM 2011-10-07 13:24:24

回答

267

首先你要絕對確保你需要通過每行迭代 - 基於集合的操作將執行在任何情況下更快,我能想到的,通常會使用更簡單的代碼。

根據您的數據有可能只使用select語句循環,如下圖所示:

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只需要觸摸的第一個(見下面的Josef's answer)。

2

您可以使用while循環:

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

    Delete From #TempTable Where x = x 
End 
14

這裏是我會怎麼做:

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 
0

我同意前面的貼子,基於集合的操作通常會表現得更好,但是如果你需要遍歷此行的做法我想借此:

  1. 添加一個新的字段到您的表變量(數據類型位,默認爲0)
  2. 插入您的數據
  3. 選擇前1行fUsed = 0 (注意:fUsed是名稱在步驟1)領域的
  4. 執行則需要通過設置融合= 1備案
  5. 從表中重複選擇下一個未使用的記錄做

  6. 更新記錄在表變量的任何處理過程

    DECLARE @databases TABLE 
    ( 
        DatabaseID int, 
        Name  varchar(15),  
        Server  varchar(15), 
        fUsed  BIT DEFAULT 0 
    ) 
    
    -- insert a bunch rows into @databases 
    
    DECLARE @DBID INT 
    
    SELECT TOP 1 @DBID = DatabaseID from @databases where fUsed = 0 
    
    WHILE @@ROWCOUNT <> 0 and @DBID IS NOT NULL 
    BEGIN 
        -- Perform your processing here 
    
        --Update the record to "used" 
    
        UPDATE @databases SET fUsed = 1 WHERE DatabaseID = @DBID 
    
        --Get the next record 
        SELECT TOP 1 @DBID = DatabaseID from @databases where fUsed = 0 
    END 
    
108

只是一個快速的注意,如果您使用的是SQL Server中,有例子:

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

會和

While EXISTS(SELECT * From #Temp) 

得到更好的服務伯爵將要接觸的每一個行的表中,EXISTS只需要觸摸的第一個。

7

如果沒有其他選擇,可以逐行創建FAST_FORWARD遊標。這將會像建立一個時間循環一樣快,並且更容易長期維護。

FAST_FORWARD 指定啓用了性能優化的FORWARD_ONLY,READ_ONLY遊標。如果還指定了SCROLL或FOR_UPDATE,則無法指定FAST_FORWARD。

15

定義你的臨時表這樣的 -

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 
2

我真不明白你爲什麼會需要求助於使用可怕的cursor點。 但這裏是另一種選擇,如果你使用的是SQL Server版本2005/2008
使用遞歸

declare @databases table 
(
    DatabaseID int, 
    Name  varchar(15), 
    Server  varchar(15) 
) 

--; Insert records into @databases... 

--; Recurse through @databases 
;with DBs as (
    select * from @databases where DatabaseID = 1 
    union all 
    select A.* from @databases A 
     inner join DBs B on A.DatabaseID = B.DatabaseID + 1 
) 
select * from DBs 
31

這是我要做的事:

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列必須是唯一的整數,因爲大多數主鍵都是。

1

我打算提供基於集合的解決方案。

insert @databases (DatabaseID, Name, Server) 
select DatabaseID, Name, Server 
From ... (Use whatever query you would have used in the loop or cursor) 

這比任何循環技術都快,而且易於編寫和維護。

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

另一種方法,而無需改變你的架構或使用臨時表:

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 
1

這將在SQL SERVER 2012版本。

declare @Rowcount int 
select @Rowcount=count(*) from AddressTable; 

while(@Rowcount>0) 
    begin 
select @[email protected]; 
SELECT * FROM AddressTable order by AddressId desc OFFSET @Rowcount ROWS FETCH NEXT 1 ROWS ONLY; 
end 
0

這是我使用2008 R2的代碼。此代碼,我使用的是建立在重點領域指標(SSNO & EMPR_NO)N的所有故事

if object_ID('tempdb..#a')is not NULL drop table #a 

select 'IF EXISTS (SELECT name FROM sysindexes WHERE name ='+CHAR(39)+''+'IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+char(39)+')' 
+' begin DROP INDEX [IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+'] ON '+table_schema+'.'+table_name+' END Create index IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+ ' on '+ table_schema+'.'+table_name+' ('+COLUMN_NAME+') ' 'Field' 
,ROW_NUMBER() over (order by table_NAMe) as 'ROWNMBR' 
into #a 
from INFORMATION_SCHEMA.COLUMNS 
where (COLUMN_NAME like '%_SSNO_%' or COLUMN_NAME like'%_EMPR_NO_') 
    and TABLE_SCHEMA='dbo' 

declare @loopcntr int 
declare @ROW int 
declare @String nvarchar(1000) 
set @loopcntr=(select count(*) from #a) 
set @ROW=1 

while (@ROW <= @loopcntr) 
    begin 
     select top 1 @String=a.Field 
     from #A a 
     where a.ROWNMBR = @ROW 
     execute sp_executesql @String 
     set @ROW = @ROW + 1 
    end 
0

選擇@pk = @pk + 1會更好:SET @pk + = @pk。避免使用SELECT如果不引用表只是分配值。

0

第1步:在select語句下面爲每個記錄創建一個具有唯一行號的臨時表。

select eno,ename,eaddress,mobno int,row_number() over(order by eno desc) as rno into #tmp_sri from emp 

第二步:聲明所需的變量

DECLARE @ROWNUMBER INT 
DECLARE @ename varchar(100) 

第三步:以總行從臨時表計數

SELECT @ROWNUMBER = COUNT(*) FROM #tmp_sri 
declare @rno int 

第四步:根據唯一的行數環路臨時表在臨時

創建
while @rownumber>0 
begin 
    set @[email protected] 
    select @ename=ename from #tmp_sri where [email protected] **// You can take columns data from here as many as you want** 
    set @[email protected] 
    print @ename **// instead of printing, you can write insert, update, delete statements** 
end 
2

重量輕,無需進行額外的表,如果你在桌子上

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

    --Process @anything 

END 
0

這種方法有一個整數ID只需要一個變量,並且不會@databases刪除任何行。我知道這裏有很多答案,但我沒有看到一個使用MIN來獲取你的下一個ID的例子。

DECLARE @databases TABLE 
(
    DatabaseID int, 
    Name  varchar(15), 
    Server  varchar(15) 
) 

-- insert a bunch rows into @databases 

DECLARE @CurrID INT 

SELECT @CurrID = MIN(DatabaseID) 
FROM @databases 

WHILE @CurrID IS NOT NULL 
BEGIN 

    -- Do stuff for @CurrID 

    SELECT @CurrID = MIN(DatabaseID) 
    FROM @databases 
    WHERE DatabaseID > @CurrID 

END 
1

我更喜歡使用偏移取,如果你有一個唯一的ID,你可以通過你的排序表:

DECLARE @TableVariable (ID int, Name varchar(50)); 
DECLARE @RecordCount int; 
SELECT @RecordCount = COUNT(*) FROM @TableVariable; 

WHILE @RecordCount > 0 
BEGIN 
SELECT ID, Name FROM @TableVariable ORDER BY ID OFFSET @RecordCount - 1 FETCH NEXT 1 ROW; 
SET @RecordCount = @RecordCount - 1; 
END 

這樣,我並不需要字段添加到表或使用窗口功能。

1

這是可能的使用遊標做到這一點:

創建函數[DBO] .f_teste_loop 返回@tabela表 ( 鱈INT, 諾姆VARCHAR(10) ) 作爲 開始

insert into @tabela values (1, 'verde'); 
insert into @tabela values (2, 'amarelo'); 
insert into @tabela values (3, 'azul'); 
insert into @tabela values (4, 'branco'); 

return; 

創建過程[DBO]。[sp_teste_loop] 作爲 開始

DECLARE @cod int, @nome varchar(10); 

DECLARE curLoop CURSOR STATIC LOCAL 
FOR 
SELECT 
    cod 
    ,nome 
FROM 
    dbo.f_teste_loop(); 

OPEN curLoop; 

FETCH NEXT FROM curLoop 
      INTO @cod, @nome; 

WHILE (@@FETCH_STATUS = 0) 
BEGIN 
    PRINT @nome; 

    FETCH NEXT FROM curLoop 
      INTO @cod, @nome; 
END 

CLOSE curLoop; 
DEALLOCATE curLoop; 

1

這裏是我的解決方案,它利用一個無限循環中,BREAK語句和@@ROWCOUNT功能。沒有遊標或臨時表是必要的,我只需要編寫一個查詢來獲取在@databases表中的下一行:

declare @databases table 
(
    DatabaseID int, 
    [Name]  varchar(15), 
    [Server]  varchar(15) 
); 


-- Populate the [@databases] table with test data. 
insert into @databases (DatabaseID, [Name], [Server]) 
select X.DatabaseID, X.[Name], X.[Server] 
from (values 
    (1, 'Roger', 'ServerA'), 
    (5, 'Suzy', 'ServerB'), 
    (8675309, 'Jenny', 'TommyTutone') 
) X (DatabaseID, [Name], [Server]) 


-- Create an infinite loop & ensure that a break condition is reached in the loop code. 
declare @databaseId int; 

while (1=1) 
begin 
    -- Get the next database ID. 
    select top(1) @databaseId = DatabaseId 
    from @databases 
    where DatabaseId > isnull(@databaseId, 0); 

    -- If no rows were found by the preceding SQL query, you're done; exit the WHILE loop. 
    if (@@ROWCOUNT = 0) break; 

    -- Otherwise, do whatever you need to do with the current [@databases] table row here. 
    print 'Processing @databaseId #' + cast(@databaseId as varchar(50)); 
end 
相關問題