هل هناك طريقة للتكرار عبر متغير جدول في TSQL دون استخدام المؤشر؟

StackOverflow https://stackoverflow.com/questions/61967

  •  09-06-2019
  •  | 
  •  

سؤال

لنفترض أن لدي متغير الجدول البسيط التالي:

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

هل الإعلان عن المؤشر واستخدامه هو خياري الوحيد إذا أردت التكرار خلال الصفوف؟هل هناك طريقة أخرى؟

هل كانت مفيدة؟

المحلول

أولاً، يجب أن تكون متأكدًا تمامًا من أنك بحاجة إلى التكرار خلال كل صف - فالعمليات القائمة على المجموعة ستؤدي بشكل أسرع في كل حالة يمكنني التفكير فيها وستستخدم عادةً تعليمات برمجية أبسط.

اعتمادًا على بياناتك، قد يكون من الممكن تنفيذ التكرار باستخدام عبارات محددة فقط كما هو موضح أدناه:

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 يحتاج فقط إلى لمس الأول (انظر جواب جوزيف أقل).

نصائح أخرى

مجرد ملاحظة سريعة، إذا كنت تستخدم SQL Server (2008 وما فوق)، فإن الأمثلة التي تحتوي على:

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

سيكون من الأفضل تقديمه مع

While EXISTS(SELECT * From #Temp)

سيتعين على الكونت أن يلمس كل صف في الجدول EXISTS يحتاج فقط للمس الأول.

هذه هي الطريقة التي أفعل ذلك:

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 عددًا صحيحًا فريدًا، كما هو الحال مع معظم المفاتيح الأساسية.

حدد الجدول المؤقت الخاص بك مثل هذا -

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

هنا كيف سأفعل ذلك:

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

إذا لم يكن لديك خيار سوى الانتقال صفًا تلو الآخر وإنشاء مؤشر FAST_FORWARD.سيكون الأمر بنفس سرعة إنشاء حلقة زمنية وأسهل بكثير في صيانته على المدى الطويل.

Fast_Forward يحدد forward_only ، read_only المؤشر مع تمكين تحسينات الأداء.لا يمكن تحديد FAST_FORWARD إذا تم تحديد SCROLL أو FOR_UPDATE أيضًا.

طريقة أخرى دون الحاجة إلى تغيير المخطط الخاص بك أو استخدام الجداول المؤقتة:

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

يمكنك استخدام حلقة while:

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

    Delete From #TempTable Where x = x
End
-- [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

خفيف الوزن، دون الحاجة إلى عمل جداول إضافية، إذا كان لديك عدد صحيح ID على الطاولة

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

  --Process @anything

END

أنا حقًا لا أرى النقطة التي تجعلك بحاجة إلى اللجوء إلى استخدام اللعين 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

سأقدم الحل القائم على المجموعة.

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

يعد هذا أسرع بكثير من أي تقنية تكرارية وأسهل في الكتابة والصيانة.

سيعمل هذا في إصدار SQL Server 2012.

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

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

أتفق مع المنشور السابق بأن العمليات المستندة إلى المجموعة عادةً ما تؤدي أداءً أفضل، ولكن إذا كنت بحاجة إلى التكرار على الصفوف، فإليك النهج الذي سأتبعه:

  1. إضافة حقل جديد إلى متغير الجدول الخاص بك (نوع البيانات، الافتراضي 0)
  2. أدخل بياناتك
  3. حدد الصف الأول العلوي حيث fUsed = 0 (ملحوظة:fUsed هو اسم الحقل في الخطوة 1)
  4. قم بإجراء أي معالجة تحتاج إلى القيام بها
  5. قم بتحديث السجل في متغير الجدول الخاص بك عن طريق تعيين fUsed = 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
    

يتطلب هذا الأسلوب متغيرًا واحدًا فقط ولا يحذف أي صفوف من @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

أفضل استخدام Offset Fetch إذا كان لديك معرف فريد، فيمكنك فرز الجدول الخاص بك حسب:

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

بهذه الطريقة لا أحتاج إلى إضافة حقول إلى الجدول أو استخدام وظيفة النافذة.

من الممكن استخدام المؤشر للقيام بذلك:

إنشاء وظيفة [DBO] .f_teste_loop إرجاع Tabela Table (COD int ، Nome 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;

نهاية

هذا هو الحل الخاص بي، والذي يستخدم حلقة لا نهائية، 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

هذا هو الكود الذي أستخدمه 2008 R2.هذا الرمز الذي أستخدمه هو إنشاء فهارس في الحقول الرئيسية (SSNO & EMPR_NO) في جميع الحكايات

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 

حدد @pk = @pk + 1 سيكون أفضل:SET @pk += @pk.تجنب استخدام SELECT إذا كنت لا تقوم بالرجوع إلى الجداول، بل تقوم فقط بتعيين القيم.

الخطوة 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 @rno=@rownumber
  select @ename=ename from #tmp_sri where rno=@rno  **// You can take columns data from here as many as you want**
  set @rownumber=@rownumber-1
  print @ename **// instead of printing, you can write insert, update, delete statements**
end
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top