it-swarm-eu.dev

Existuje způsob, jak procházet proměnnou tabulky v SQL bez použití kurzoru?

Řekněme, že mám následující jednoduchou proměnnou tabulky:

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

Je deklarování a používání kurzoru mou jedinou možností, pokud chci iterovat řadami? Existuje jiná cesta?

220
Ray Vega

Nejprve byste si měli být naprosto jistí, že musíte iterovat přes jednotlivé řádky - operace založené na sadě budou fungovat rychleji v každém případě, na který si pomyslím, a obvykle použiji jednodušší kód.

V závislosti na vašich datech může být možné opakovat smyčky pouze pomocí vybraných příkazů, jak je uvedeno níže:

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

Další alternativou je použití dočasné tabulky:

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

Možnost, kterou byste si měli vybrat, opravdu závisí na struktuře a objemu vašich dat.

Poznámka: Pokud používáte SQL Server, měli byste být lépe obsluhováni pomocí:

WHILE EXISTS(SELECT * FROM #Temp)

Pomocí COUNT se bude muset dotknout každého jednotlivého řádku v tabulce, EXISTS se musí dotknout pouze prvního (viz Josefova odpověď níže).

331
Martynnw

Jen rychlou poznámku, pokud používáte SQL Server (2008 a vyšší), příklady, které mají:

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

Bude lépe sloužit

While EXISTS(SELECT * From #Temp)

Počet se bude muset dotknout každého jednotlivého řádku v tabulce, EXISTS se musí dotknout pouze prvního.

124
Josef

Takto to dělám:

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

Žádné kurzory, žádné dočasné tabulky, žádné další sloupce. Sloupec USERID musí být jedinečné celé číslo, protože většina primárních klíčů je.

36
Trevor

Definujte svou dočasnou tabulku takto:

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

Pak to udělejte -

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
20
Seibar

Tady je, jak bych to udělal:

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

[Upravit] Protože jsem pravděpodobně přeskočil slovo „proměnná“, když jsem poprvé četl otázku, je zde aktualizovaná odpověď ...


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
16
leoinfo

Pokud nemáte jinou možnost, než jít po řádku, vytvořit kurzor FAST_FORWARD. Bude to tak rychlé, jako když se buduje smyčka, a mnohem snazší je udržet na dlouhé trase.

FAST_FORWARD Určuje FORWARD_ONLY, READ_ONLY kurzor s povolenou optimalizací výkonu. FAST_FORWARD nelze zadat, pokud je také zadán SCROLL nebo FOR_UPDATE.

9
Wes Brown

Další přístup, aniž byste museli měnit své schéma nebo používat dočasné tabulky:

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
4
SReiderB

Lehký, aniž byste museli vytvářet další tabulky, pokud máte v tabulce celé ID

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
3
Control Freak

Můžete použít smyčku while:

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

    Delete From #TempTable Where x = x
End
3
GateKiller
-- [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
Syed Umar Ahmed

Opravdu nechápu, proč byste se museli uchýlit k používání obávaný cursor. Ale zde je další možnost, pokud používáte SQL Server verze 2005/2008
Použijte Rekurze

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
2
Sung M. Kim

Toto bude fungovat ve verzi 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 
2
OrganicCoder

Budu poskytovat řešení založené na sadě.

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

To je mnohem rychlejší než jakákoli opakující se technika a je snazší psát a udržovat.

2
HLGEM

Tento přístup vyžaduje pouze jednu proměnnou a neodstraní žádné řádky z @ datových databází. Vím, že je zde mnoho odpovědí, ale nevidím tu, která používá MIN k získání vašeho dalšího ID, jako je tento.

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
Sean

K tomu je možné použít kurzor:

create function [dbo] .f_teste_loop vrátí @tabela tabulku (cod int, nome varchar (10)) jako začátek

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;

konec

vytvořte postup [dbo]. [sp_teste_loop] jako začátek

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;

konec

1

Tady je moje řešení, které využívá nekonečnou smyčku, příkaz BREAK a funkci @@ROWCOUNT. Nejsou nutné žádné kurzory ani dočasná tabulka a já stačí napsat jeden dotaz, abych získal další řádek v tabulce @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
1
Mass Dot Net

Souhlasím s předchozím příspěvkem, že operace založené na sadě budou obvykle fungovat lépe, ale pokud potřebujete opakovat přes řádky, postupuji takto:

  1. Přidejte do své proměnné tabulky nové pole (Datový typ Bit, výchozí 0)
  2. Vložte svá data
  3. Vyberte první řádek, kde fUsed = 0 (Poznámka: fUsed je název pole v kroku 1)
  4. Proveďte jakékoli zpracování, které potřebujete
  5. Aktualizujte záznam v tabulce proměnné nastavením fUsed = 1 pro záznam
  6. Z tabulky vyberte další nepoužitý záznam a postup opakujte

    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
    
1
Tim Lentine

Dávám přednost použití offsetu načtení, pokud máte jedinečné ID, které můžete uspořádat podle:

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

Tímto způsobem nemusím do tabulky přidávat pole ani používat funkci okna.

1
Yves A Martin

Krok 1: Pod příkazem select se vytvoří dočasná tabulka s jedinečným číslem řádku pro každý záznam.

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

Krok 2: Deklarujte požadované proměnné

DECLARE @ROWNUMBER INT
DECLARE @ename varchar(100)

Krok 3: Vezměte celkový počet řádků z dočasné tabulky

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

Krok 4: Smyčka temp tabulky založené na jedinečné číslo řádku vytvořit v temp

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
0
Srinivas Maale

Toto je kód, který používám 2008 R2. Tento kód, který používám, je vytváření indexů na klíčových polích (SSNO & EMPR_NO) n všech příběhů

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
howmnsk

Vyberte @pk = @pk + 1 by bylo lepší: SET @pk + = @pk. Vyhněte se použití SELECT, pokud nejste referenční tabulky jsou pouze přiřazování hodnot.

0
Bob Alley