想要推薦的方法,是使用 while exists ( ... ) 來解決。
while exists 範例:
declare @seqsn bigint set @seqsn=682 declare @id bigint select top 1 @id=gcf_id from prog_checkpoint_first where gcf_seqsn=@seqsn order by gcf_id --Do Your Job here print @id set @id=@id+1 while exists (select * from prog_checkpoint_first where gcf_seqsn=@seqsn and gcf_id>=@id) begin --Do Your Job here select top 1 @id=gcf_id from prog_checkpoint_first where gcf_seqsn=@seqsn and gcf_id>=@id order by gcf_id print @id set @id=@id+1 end
使用暫存資料表的方法:
declare @seqsn bigint set @seqsn=682 declare @id bigint select gcf_id into #tmp from prog_checkpoint_first where gcf_seqsn=@seqsn while (select count(*) from #tmp)>0 begin select top 1 @id=gcf_id from #tmp --Do Your Job here print @id delete #tmp where gcf_id = @id end drop table #tmp
我接著在自己的資料庫上建了一個測試資料表:prog_checkpoint_first
CREATE TABLE [dbo].[prog_checkpoint_first]( [gcf_id] [bigint] IDENTITY(1,1) NOT NULL, [gcf_seqsn] [bigint] NOT NULL, CONSTRAINT [PK_prog_checkpoint_first] PRIMARY KEY CLUSTERED ( [gcf_id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
緊接著,就是新增測試資料。我新增了2萬筆資料進去,然後分別執行「while exists 範例」與「使用暫存資料表的方法」
declare @count bigint set @count=1 while @count<=20000 begin insert into prog_checkpoint_first (gcf_seqsn) values (682) set @count=@count+1 end
最後觀察這兩種方式的執行時間。
「while exists 範例」:2 秒
「使用暫存資料表的方法」:34秒
這結果,真的差很大。所以,當你有需要透過 SQL去處理迴圈問題,如果可預期資料量很大,那會建議使用 while exists 的寫法。如果資料量很小,則用那種都好。
補充一下為何二個範例會有不小的效能差異
回覆刪除1.暫存資料表是存在tempdb,所以當我們一定要產生暫存資料表時,在SELECT LIST上只要列出"你要的"的欄位,以免產生過多不必要的I/O動作,列如SELECT * 應該避免。
2.另一個影響效能的地方是EXISTS與COUNT(*),基本上EXISTS的效能是比COUNT(*)好,至於原因請參考網址http://www.sqlmag.com/article/tsql3/exists-vs-count-.aspx
說得太好了,
回覆刪除謝謝補充。
剛剛看了 Ken 提供的參考網頁。的確,使用 count(*) ,即使已經找到一筆資料,但他還是要把所有資料都跑完才停止。而使用 exists(),則只要找到一筆資料,後面就不會繼續執行了。這兩種方法經這麼一比較,真的效能上的差異就明顯多了。感謝 Ken 的補充,真是獲益良多。
回覆刪除補充一段比較簡潔的 code:
回覆刪除---------------------------------
declare @guid nvarchar(50)
set @guid='64536941-4476-4342-96b2-fa6584597ac8'
declare @gatt_id bigint
declare @tmp_id bigint
if exists(select tmp_id from tmp_upload_file where tmp_guid=@guid)
begin
--將 @tmp_id 設為最小的值
set @tmp_id=-1
while exists (select tmp_id from tmp_upload_file where tmp_guid=@guid and tmp_id>@tmp_id )
begin
select top 1 @gatt_id=gatt_id,@tmp_id=tmp_id from tmp_upload_file where tmp_guid=@guid and tmp_id>@tmp_id order by tmp_id
--Do
print @gatt_id
end
end
補充一段針對字串的處理方式
回覆刪除----------------------------------
declare @strExcludes nvarchar(max)
set @strExcludes='B,C'
declare @tmp_id nvarchar(50)
if exists(SELECT * FROM [dbo].[fn_split] (@strExcludes ,',') )
begin
--將 @tmp_id 設為最小的值
set @tmp_id=''
while exists (select [Value] from [dbo].[fn_split] (@strExcludes ,',') where [Value]>@tmp_id )
begin
select top 1 @tmp_id=[Value] from [dbo].[fn_split] (@strExcludes ,',') where [Value]>@tmp_id order by [Value]
--Do
print 'p'
end
end
自己曾經將資料格式為 varchar() 的數字內容,如: "1","2","11","12" 等資料當作是 key 值來使用 while exists ,發生了有些項目該執行卻沒有執行的情況。後來發現問題出在 Order by 時,"11" 比 "2" 排得還要前面,也就是 "11" < "2" 。所以,如果確定是要用數字格式來當 Key,千萬別偷懶,一定要確保該欄位格式為數字,否則要用 convert(int,[欄位]) 來轉換。
回覆刪除select ROW_NUMBER() over (order by abd_gcarstyleno,abd_gcarstyleno2) RowID,*
回覆刪除into #tmp
from eea_valid
where abd_vechiletype='E' and CHARINDEX('-',abd_gcarstyleno)>0
declare @tmp_id bigint
--將 @tmp_id 設為最小的值
set @tmp_id=-1
while exists (select RowID from #tmp where RowID>@tmp_id )
begin
select top 1 @tmp_id=RowID from #tmp where RowID>@tmp_id order by RowID
--Do
print @gatt_id
end
drop table #tmp