- 目錄
1.游標簡介
1.0 理解定義
SQL游標(cursor)是一個數據庫對象,用於從結果集中檢索某一行的數據。
游標是系統為用戶開設的一個數據緩沖區,存放SQL語句的執行結果。每個游標區都有一個名字,用戶可以用SQL語句逐一從游標中獲取記錄,並賦給主變量,交由主語言進一步處理。
在編程中,我們使用諸如for
或while
之類的循環一次遍歷一項,游標遵循相同的方法。當在SQL中,應用程序邏輯需要一次只處理一行,而不是一次處理整個結果集。可以使用游標完成此操作。
怎么理解“為了處理查詢的結果集中特定行的數據,我們使用游標處理”? 其實,游標的英文單詞是cursor,也可以翻譯為光標,其實類比我們編輯文檔,當想要編輯具體的某一行的時候,我們需要使用光標移到該行進行編輯,在SQL中游標的作用是一樣的。
當然,本質上就是個定義在結果集上的指針,我們可以控制該指針遍歷結果集。
這里補充一下:理論上SQL編寫是按照面向集合的思維模式,而我們使用游標則又回到了面向過程的思維模式。此中思想非三言二語可說明白的,相關知識可以參考《SQL進階教程》2.6章節!
1.1 游標的主要作用
- 定位到結果集中的某一行。
- 對當前位置的數據進行讀寫。
- 可以對結果集中的數據單獨操作,而不是整行執行相同的操作。
- 是面向集合的數據庫管理系統和面向行的程序設計之間的橋梁。
1.2 游標的優缺點
- 優點:參考上文中游標的作用
- 缺點:濫用游標會影響系統性能。
一般來說,有一個共識:能不用游標就不要用游標。
事實上,編寫SQL語句的時候大多數的情形下是沒有必要使用游標的。
1.3 游標生命周期
游標的生命周期:
- 聲明游標(Declare Cursor)
- 打開游標(Open Cursor)
- 提取游標(Fetch Cursor)
- 關閉游標(Close Cursor)
- 釋放游標(Deallocate Cursor)
使用游標的過程如下:
1.4 基本語法
①完整的聲明游標
DECLARE cursor_name CURSOR [ LOCAL | GLOBAL ]
[ FORWARD_ONLY | SCROLL ]
[ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ]
[ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]
[ TYPE_WARNING ]
FOR select_statement
[ FOR UPDATE [ OF column_name [ ,...n ] ] ]
【說明】方括號中的關鍵之是可選的,具體作用如下:
-
作用域
- Local:游標作用域為局部,只在定義它的批處理、函數和存儲過程中有效。
- Global:游標作用域為全局,由連接執行的任何存儲過程或批處理中,都可以引用該游標。
- 默認值是Local
-
游標方向
- Forward_Only:指定游標智能從第一行滾到最后一行,種游標稱為:只進游標。
- Scroll:指定游標在定義的數據集中向任何方向,或任何位置移動。
- 默認是Forward_Only
-
游標讀取的數據和基表數據關系
-
Static表明:游標一旦指定了select查詢出的結果集,之后任何對於基表(即:select語句所查詢的表)內數據的更改不會影響到游標的內容。該種游標稱為靜態游標
-
Dynamic和Static完全相反的選項,當底層數據庫更改時,游標的內容也隨之得到反映,在下一次fetch中,數據內容會隨之改變。該種游標稱為動態游標。
-
KeySet:指明當再游標被打開時游標中的列的順序時固定的,游標只維持其所依賴的基表的鍵
-
Fast_Forward:指明一個Forward_Only且Read_Only型游標。注意:一旦聲明了Fast_Forward,則之前就不可以選擇Scroll類型的游標。同樣,在之后也就不能使用Scroll_Locks和Optimistic選項。
-
默認值是Dynamic
-
-
游標是否鎖定數據
-
Read_Only意味着聲明的游標只能讀取數據,游標不能做任何更新操作
-
Scroll_Locks是另一種極端,將讀入游標的所有數據進行鎖定,防止其他程序進行更改,以確保更新的絕對成功
-
Optimistic是相對比較好的一個選擇,不鎖定任何數據,當需要在游標中更新數據時,如果底層表數據更新,則游標內數據更新或刪除會不成功,如果,底層表數據未更新,則游標內表數據可以更新或刪除
-
-
Type_Warning:指明若游標類型被修改成與用戶定義的類型不同時,將發送一個警告信息給客戶端。
-
Update[Of colunm_name[,...n]]:定義利用游標可更新的列。若果列出了Of colunm_name[,...n],則只允許修改列出的列
-
其實,從上面可以看出游標的聲明是有許多的可選項。
但是一般來說,只要記住游標聲明的默認值。一般實際開發中,如無必要則使用默認值即可。
②打開游標
OPEN cursor_name
③提取行數據到指定的變量列表中
--提取下一行數據
FETCH NEXT FROM cursor_name INTO variateList;
--提取上一行數據
FETCH PRIOR FROM cursor_name INTO variateList;
--提取第一行數據
FETCH FIRST FROM cursor_name INTO variateList;
--提取最后一行數據
FETCH LAST FROM cursor_name INTO variateList;
--提取第3行數據(提取指定的行)
FETCH ABSOLUTE 3 FROM cursor_name INTO variateList;
--提取當前行的上一行(復數為向后,正數為向前)
FETCH RELATIVE -1 FROM cursor_name INTO variateList;
【注意】:
-
游標只有上述的6種移動方式,但是要注意的是:一旦在聲明游標的時候,定義為Forward_Only(默認值),則提取行數據中時候,只能是Fetch next
-
INTO
列表中聲明的變量數目必須與所選列的數目相同。即:select的結果集中有幾列,則INTO后的變量就該有幾個。
④關閉游標
CLOSE cursor_name
⑤釋放游標
DEALLOCATE cursor_name
2.游標示例
2.0 准備測試數據
USE [db_Tome1]
GO
CREATE TABLE [dbo].[szmUser]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserName] [nchar](10) NULL
)
Insert into szmUser (UserName) values (N'張三'),(N'李四'),(N'王五'),(N'趙六'), (N'Tom'),(N'Jerry'),(N'Bob');
GO
2.1 示例1-FORWARD_ONLY類型游標
使用FORWARD_ONLY聲明只進游標,實現從頭到尾提取行數據
DECLARE test_cur CURSOR FORWARD_ONLY --聲明游標,定義為FORWARD_ONLY類型
FOR SELECT * FROM szmUser--游標作用的結果集
OPEN test_cur --打開游標
DECLARE @userId INT ,@userName NCHAR(10)--聲明標量用於存儲行數據
WHILE ( @@fetch_status = 0 )
BEGIN
FETCH NEXT FROM test_cur INTO @userId ,@userName--提取下一行數據並存入定義的變量中
PRINT @userName--打印數據
END
CLOSE test_cur--關閉游標
DEALLOCATE test_cur--釋放游標
消息框打印信息如下:
張三
李四
王五
趙六
Tom
Jerry
Bob
Bob
【注意】:
-
全局變量
@@Fetch_Status
的值表示游標提取狀態信息,該狀態用於判斷Fetch語句返回數據的有效性。
當執行一條Fetch語句之后,@@Fetch_Status
可能出現3種值:狀態碼 含義 0 Fetch語句成功 -1 Fetch語句失敗或行不在結果集中 -2 提取的行不存在 -
這里聲明的游標定義為
FORWARD_ONLY
類型,所以只能使用FETCH NEXT
提取數據,若是使用其他的提取數據的方式則會報錯,比如使用FETCH LAST
,則報錯:
fetch: 提取類型 last 不能與只進游標一起使用。
2.2 示例2-SCROLL類型游標
使用SCROLL聲明游標,實現讀取特定行數據
DECLARE test_cur CURSOR scroll --聲明游標,定義為FORWARD_ONLY類型
FOR SELECT * FROM szmUser--游標作用的結果集
OPEN test_cur --打開游標
DECLARE @userId INT ,@userName NCHAR(10)--聲明標量用於存儲行數據
FETCH FIRST FROM test_cur INTO @userId, @userName--提取當前結果集的第一行
PRINT CAST(@userId as varchar)+':'+@userName
FETCH LAST FROM test_cur INTO @userId ,@userName--提取當前結果集的最后一行
PRINT CAST(@userId as varchar)+':'+@userName
FETCH prior From test_cur INTO @userId ,@userName--提取當前游標指向的上一行數據
PRINT CAST(@userId as varchar)+':'+@userName
FETCH ABSOLUTE 2 FROM test_cur INTO @userId ,@userName--提取當前結果集中的第二行數據
PRINT CAST(@userId as varchar)+':'+@userName
FETCH RELATIVE 1 FROM test_cur INTO @userId ,@userName--提取當前游標指向的下一行數據
PRINT CAST(@userId as varchar)+':'+@userName
FETCH RELATIVE -1 FROM test_cur INTO @userId ,@userName--提取當前游標指向的上一行數據
PRINT CAST(@userId as varchar)+':'+@userName
CLOSE test_cur--關閉游標
DEALLOCATE test_cur--釋放游標
消息框打印信息如下:
1:張三
7:Bob
6:Jerry
2:李四
3:王五
2:李四
2.3 示例3-使用游標進行更新和刪除數據
使用游標對結果集中數據進行更改和刪除
示例:刪除SELECT * FROM szmUser
結果集中的名叫張三的的人,同時將該結果集中名叫李四的名字改為李四四
DECLARE @userId int ,@userName nchar(10)
DECLARE test_cur CURSOR SCROLL
FOR SELECT * FROM szmUser
OPEN test_cur
FETCH First FROM test_cur INTO @userId,@userName--定位游標到第一行(注意這里,一定要將游標首先定位到某一行)
WHILE (@@FETCH_STATUS=0)
BEGIN
IF @userName='李四'
BEGIN
Update szmUser Set UserName='李四四' WHERE CURRENT OF test_cur --修改當前行
END
IF @userName='張三'
BEGIN
DELETE szmUser WHERE CURRENT OF test_cur --刪除當前行
END
FETCH NEXT FROM test_cur INTO @userId ,@userName --移動游標
END
CLOSE test_cur
DEALLOCATE test_cur
【注意】:
-
在這里使用while循環一定要首先將定位游標的起始位置,類比其它類型的編程語言中循環語句,循環就要有起始位置,步長,結束位置
-
注意:一開始,使用的測試表雖然定義了標識規范及標識增量,但是沒有定義主鍵,測試的時候報錯:
游標是只讀的。 語句已終止。
,其實只是因為表沒有主鍵或唯一性約束,所以CURRENT OF test_cur
會報錯當然,也是可以在更新或刪除語句中使用where指定具體的記錄。
2.4 示例4-靜態游標和動態游標演示
2.4.0 說明
游標在聲明的時候,可以定義是靜態游標還是動態游標,游標默認是動態游標。
靜態游標在打開時會將數據集存儲在tempdb中,因此顯示的數據與游標打開時的數據集保持一致,在游標打開以后對數據庫的更新不會顯示在游標中。
動態游標在打開后會反映對數據庫的更改。所有UPDATE、INSERT 和 DELETE 操作都會顯示在游標的結果集中,結果集中的行數據值、順序和成員在每次提取時都會改變。
簡而言之:靜態游標的數據是固定的,不會因為基表的改變而改變;動態游標的數據是隨着基表變化而變化的。
2.4.1 示例-靜態游標
DECLARE @userId INT , @userName NCHA(10) --聲明變量,存儲行數據
DECLARE test_cur CURSOR STATIC --聲明靜態游標
FOR SELECT * FROM szmUser --游標遍歷的結果集
OPEN test_cur --打開游標
FETCH NEXT FROM test_cur INTO @userId,@userName --取數據
WHILE ( @@fetch_status = 0) --判斷是否還有據
BEGIN
PRINT RTRIM(@userId) +':'+ @userName
UPDATE szmUser SET UserName='測試' WHEREid=4 --測試靜態動態用
FETCH NEXT FROM test_cur INTO @userId,@userName --游標進入下一行
END
CLOSE test_cur
DEALLOCATE test_cur
運行結果:
2:李四
3:王五
4:趙六
5:Tom
6:Jerry
7:Bob
8:Mark
【說明】:我們定義的是靜態游標,所以一旦當結果集進游標區后,基表的數據發生改變游標讀取數據依舊是最初入游標區的數據。
所以在這里,當游標提取一行數據后,我們就把基表中id=的userName改為“測試”,但是游標繼續執行,讀取的還是初進入游標區的數據,即id=4,userName=趙六
2.4.2 示例-動態游標
聲明游標的時候,默認就是動態游標,所以這里我們只要把上面的代碼中的STATIC
刪除即可,運行結果如下,你好發現在基表中對數據的修改,直接是反應到已聲明的游標中。我們修改的id=4的用戶名,直接顯示在游標的數據中。
2:李四
3:王五
4:測試 --修改基表數據直接作用在已聲明的游標中
5:Tom
6:Jerry
7:Bob
8:Mark
2.4.3 動態和靜態區別
-
聲明游標默認是動態游標,對基表中數據的改變影響已聲明的動態游標,不影響已聲明的靜態游標。
原則是應該盡量避免使用靜態游標
-
動態游標的打開速度比靜態游標的打開速度快。當打開靜態游標時,必須生成內部臨時工作表,而動態游標則不需要。
-
在聯接中,靜態游標的速度可能比動態游標的速度快。因為動態游標在滾動時反應對結果集內的各行數據所做的更改,它會消耗資源去檢測基表的更改,因此對於復雜的查詢,且不需要反映基表的更新的游標的處理應將其定義為靜態游標。
3.使用原則
- Rule 1:能不用游標則不用游標
- 用完之后是一定要及時的關閉和釋放游標
- 不要在有大量數據的結果集中定義游標
- 盡量避免使用靜態游標
- 盡量不要在游標上更新數據
- 只進游標(First-Forward)若是只讀,可以使用Fast-Forward定義游標