2008-09-15 13 views
186

मान लें कि मेरे पास निम्न सरल तालिका चर है:क्या कर्सर का उपयोग किये बिना एसक्यूएल में टेबल वैरिएबल के माध्यम से लूप करने का कोई तरीका है?

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

अगर मैं पंक्तियों के माध्यम से पुन: प्रयास करना चाहता हूं तो कर्सर को घोषित करना और उसका एकमात्र विकल्प उपयोग करना है? क्या कोई और तरीका है?

+3

आप हमें कारण प्रदान कर सके तुम क्यों पंक्तियों पर पुनरावृति करना चाहते हैं कि पुनरावृत्ति की आवश्यकता नहीं है (और जो ज्यादातर मामलों में बड़े अंतर से तेजी से होते हैं) –

+0

पॉप के साथ सहमत हैं ... स्थिति के आधार पर कर्सर की आवश्यकता नहीं हो सकती है। लेकिन अगर आपको – Shawn

+0

की आवश्यकता है तो कर्सर का उपयोग करने में कोई समस्या नहीं है http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them – HLGEM

उत्तर

267

सबसे पहले आप पूरी तरह सुनिश्चित करें कि आप प्रत्येक पंक्ति के माध्यम से पुनरावृति करने की आवश्यकता होना चाहिए - सेट आधारित संचालन तेजी से हर मामले में मैं के बारे में सोच सकते हैं प्रदर्शन करेंगे और सामान्य रूप से होगा सरल कोड का प्रयोग करें।

अपने डेटा के आधार पर यह पाश सिर्फ चुनिंदा बयानों का उपयोग कर संभव हो सकता है जैसा कि नीचे दिखाया:

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 

विकल्प चुनना चाहिए वास्तव में आपके डेटा की संरचना और मात्रा पर निर्भर करता है।

नोट:

WHILE EXISTS(SELECT * FROM #Temp) 

COUNT, EXISTS केवल जरूरत है पहले एक को छूने के लिए उपयोग करना तालिका में हर एक पंक्ति को छूने के लिए होगा: आप एसक्यूएल सर्वर का उपयोग कर रहे हैं, तो बेहतर उपयोग करते हुए कार्य किया आप होगा (नीचे Josef's answer देखें)।

+0

"शीर्ष 1 @ आईडी = आईडी से एडीबल" चुनें "एटीबल से शीर्ष 1 @ आईडी = आईडी चुनें जहां प्रसंस्कृत = 0" – Amzath

+9

यदि SQL सर्वर का उपयोग कर रहे हैं, तो ऊपर दिए गए छोटे ट्विक के लिए जोसेफ का उत्तर नीचे देखें। – Polshgiant

+2

क्या आप समझा सकते हैं कि यह कर्सर का उपयोग करने से बेहतर क्यों है? –

2

आप थोड़ी देर के पाश का उपयोग कर सकते हैं:

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

तो मूल रूप से आप एक कर्सर कर रहे हैं, लेकिन कर्सर – Shawn

+1

के सभी लाभों के बिना ... प्रसंस्करण करते समय उपयोग की जाने वाली तालिकाओं को लॉक किए बिना ... क्योंकि यह कर्सर के * लाभ * में से एक है :) – leoinfo

+3

टेबल्स? यह एक सारणी है - कोई समवर्ती पहुंच संभव नहीं है। – DenNukem

0

मैं पिछले पोस्ट इससे सहमत सेट आधारित संचालन आम तौर पर बेहतर प्रदर्शन करेंगे, लेकिन आप पंक्तियों यहाँ पर पुनरावृति करने की आवश्यकता है, तो तरीका है मैं ले जाएगा:

  1. जोड़ें अपनी मेज चर (डेटा प्रकार बिट, डिफ़ॉल्ट 0)
  2. के लिए एक नया क्षेत्र अपने डेटा सम्मिलित करें
  3. शीर्ष 1 पंक्ति का चयन करें जहां आपस में जुड़े = 0 (नोट: आपस में जुड़े नाम है चरण 1) में क्षेत्र के
  4. जो कुछ प्रसंस्करण आप रिकॉर्ड
  5. के लिए जुड़े हुए सेटिंग = 1 टेबल और दोहराने से अगले अप्रयुक्त रिकॉर्ड का चयन करके अपने तालिका वैरिएबल में क्या करना

  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

बस एक क्विक नोट, आप एसक्यूएल सर्वर, उदाहरण है कि प्रयोग कर रहे हैं:

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

बेहतर

While EXISTS(SELECT * From #Temp) 

के साथ परोसा जा सकते हैं गणना तालिका में हर एक पंक्ति को छूने के लिए होगा, EXISTS केवल पहले एक स्पर्श की जरूरत है।

+6

यह कोई जवाब नहीं है लेकिन एक टिप्पणी/मार्टिनव उत्तर पर वृद्धि। –

+6

इस नोट की सामग्री किसी टिप्पणी की तुलना में बेहतर स्वरूपण कार्यक्षमता को मजबूर करती है, मैं उत्तर में संलग्न करने का सुझाव दूंगा। – Custodio

+1

एसक्यूएल के बाद के संस्करणों में, क्वेरी ऑप्टिमाइज़र यह समझने के लिए पर्याप्त चालाक है कि जब आप पहली चीज़ लिखते हैं, तो आप वास्तव में दूसरा मतलब रखते हैं और तालिका स्कैन से बचने के लिए इसे अनुकूलित करते हैं। –

7

यदि आपके पास कोई FAST_FORWARD कर्सर बनाने वाली पंक्ति से पंक्ति में जाने से कोई विकल्प नहीं है। यह थोड़ी देर तक लूप बनाने और लंबे समय तक बनाए रखने के लिए बहुत आसान होगा जितना तेज़ होगा।

FAST_FORWARD प्रदर्शन अनुकूलन सक्षम के साथ एक FORWARD_ONLY, READ_ONLY कर्सर निर्दिष्ट करता है। यदि SCROLL या FOR_UPDATE को भी निर्दिष्ट किया गया है तो FAST_FORWARD निर्दिष्ट नहीं किया जा सकता है।

+1

हाँ! जैसा कि मैंने कहीं और टिप्पणी की है, मुझे अभी तक कोई तर्क नहीं दिख रहा है कि क्यों ** ** ** कर्सर का उपयोग करने के लिए ** ** तालिका चर ** पर केस को फिर से चालू करने के लिए क्यों नहीं है। एक 'FAST_FORWARD' कर्सर एक अच्छा समाधान है। (upvote) – peterh

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 का सहारा करने की आवश्यकता होगी नहीं दिख रहा। लेकिन यहाँ एक और विकल्प आप एसक्यूएल सर्वर संस्करण 2005/2008
उपयोग Recursion उपयोग कर रहे हैं है

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 

कोई कर्सर, कोई अस्थायी तालिकाओं , कोई अतिरिक्त कॉलम नहीं। उपयोगकर्ता आईडी कॉलम एक अद्वितीय पूर्णांक होना चाहिए, क्योंकि अधिकांश प्राथमिक कुंजी हैं।

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

यह एसक्यूएल सर्वर 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 आर 2 का उपयोग कर रहा हूं। सेट @pk + = @pk: इस कोड है कि मैं का उपयोग कर रहा कुंजी खेतों पर अनुक्रमित (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 बेहतर होगा चुनें निर्माण करना है। चयन का उपयोग करने से बचें यदि आप तालिकाओं का संदर्भ नहीं दे रहे हैं तो वे केवल मान निर्दिष्ट कर रहे हैं।

0

चरण 1: नीचे दिए गए कथन के नीचे प्रत्येक रिकॉर्ड के लिए अद्वितीय पंक्ति संख्या वाला एक अस्थायी तालिका बनाता है।

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

चरण 2: घोषित आवश्यक चर

DECLARE @ROWNUMBER INT 
DECLARE @ename varchar(100) 

चरण 3: लो कुल पंक्तियों को अस्थायी तालिका से गिनती

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

चरण 4: लूप अस्थायी अद्वितीय पंक्ति संख्या के आधार पर तालिका अस्थायी

में बना
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 का उपयोग करता है।

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 

इस तरह से मैं मेज पर फ़ील्ड जोड़ने या एक विंडो का उपयोग करने की जरूरत नहीं है समारोह।

+0

यह मुझे "फ़ेच अगला 1 पंक्ति" पर वाक्यविन्यास त्रुटि दिखा रहा है; –

1

यह एक कर्सर का उपयोग करने के लिए क्या करने के लिए यह संभव है:

बनाने समारोह [dbo] रिटर्न @tabela तालिका .f_teste_loop ( कॉड पूर्णांक, नोम 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; 

अंत

+0

मूल प्रश्न "कर्सर का उपयोग किए बिना" नहीं था? –

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 
+0

मुझे अभी एहसास हुआ कि ** @ ControlFreak ** ने मेरे सामने इस दृष्टिकोण की सिफारिश की है; मैंने बस टिप्पणी और एक और verbose उदाहरण जोड़ा। –