2008-09-15 53 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 
相关问题