次の単純なテーブル変数があるとします。
declare @databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into @databases
行を反復処理したい場合、カーソルを宣言して使用することが唯一のオプションですか? 別の方法はありますか?
次の単純なテーブル変数があるとします。
declare @databases table
(
DatabaseID int,
Name varchar(15),
Server varchar(15)
)
-- insert a bunch rows into @databases
行を反復処理したい場合、カーソルを宣言して使用することが唯一のオプションですか? 別の方法はありますか?
まず第一に、各行を反復処理する必要があることを絶対に確認する必要があります。セットベースの操作は、考えられるすべてのケースで高速に実行され、通常はより単純なコードを使用します。
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
もう 1 つの方法は、一時テーブルを使用することです。
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 の回答を参照)。
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 カーソルを作成する以外に選択肢がない場合。while ループを構築するのと同じくらい高速であり、長期にわたって維持するのがはるかに簡単になります。
FAST_FORWARD パフォーマンスの最適化が有効な FORWARD_ONLY、READ_ONLY カーソルを指定します。SCROLLまたはFOR_UPDATEも指定されている場合、FAST_FORWARDは指定できません。
これは、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
while ループを使用できます。
While (Select Count(*) From #TempTable) > 0
Begin
Insert Into @Databases...
Delete From #TempTable Where x = x
End
dreaded の使用に頼る必要がある理由が本当にわかりません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
-- [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
セットベースのソリューションを提供します。
insert @databases (DatabaseID, Name, Server)
select DatabaseID, Name, Server
From ... (Use whatever query you would have used in the loop or cursor)
これは、どのループ手法よりもはるかに高速であり、記述と保守が容易です。
テーブルを並べ替えることができる一意の 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 つの変数のみが必要であり、@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: 以下の select ステートメントは、レコードごとに一意の行番号を持つ一時テーブルを作成します。
select eno,ename,eaddress,mobno int,row_number() over(order by eno desc) as rno into #tmp_sri from emp
Step2:必要な変数を宣言する
DECLARE @ROWNUMBER INT
DECLARE @ename varchar(100)
ステップ 3: 一時テーブルから合計行数を取得する
SELECT @ROWNUMBER = COUNT(*) FROM #tmp_sri
declare @rno int
Step4: 一意の行番号に基づいて一時テーブルをループします。
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
通常、セットベースの操作の方がパフォーマンスが高いという以前の投稿に同意しますが、行を反復処理する必要がある場合は、次のアプローチをとります。
テーブルから次の未使用のレコードを選択し、プロセスを繰り返します
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
これは、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