SQL遊標(cursor)是一個數據庫對象,用於從結果集中檢索某一行的數據。html
遊標是系統爲用戶開設的一個數據緩衝區,存放SQL語句的執行結果。每一個遊標區都有一個名字,用戶能夠用SQL語句逐一從遊標中獲取記錄,並賦給主變量,交由主語言進一步處理。sql
在編程中,咱們使用諸如for
或while
之類的循環一次遍歷一項,遊標遵循相同的方法。當在SQL中,應用程序邏輯須要一次只處理一行,而不是一次處理整個結果集。可使用遊標完成此操做。數據庫
怎麼理解「爲了處理查詢的結果集中特定行的數據,咱們使用遊標處理」? 其實,遊標的英文單詞是cursor,也能夠翻譯爲光標,其實類比咱們編輯文檔,當想要編輯具體的某一行的時候,咱們須要使用光標移到該行進行編輯,在SQL中游標的做用是同樣的。編程
固然,本質上就是個定義在結果集上的指針,咱們能夠控制該指針遍歷結果集。編程語言
這裏補充一下:理論上SQL編寫是按照面向集合的思惟模式,而咱們使用遊標則又回到了面向過程的思惟模式。此中思想非三言二語可說明白的,相關知識能夠參考《SQL進階教程》2.6章節!函數
- 定位到結果集中的某一行。
- 對當前位置的數據進行讀寫。
- 能夠對結果集中的數據單獨操做,而不是整行執行相同的操做。
- 是面向集合的數據庫管理系統和麪向行的程序設計之間的橋樑。
遊標的生命週期:sqlserver
使用遊標的過程以下:性能
①完整的聲明遊標測試
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 ] ] ]
【說明】方括號中的關鍵之是可選的,具體做用以下:fetch
做用域
遊標方向
遊標讀取的數據和基表數據關係
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
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
使用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 NEXTQ
提取數據,如果使用其餘的提取數據的方式則會報錯,好比使用FETCH LAST
,則報錯:
fetch: 提取類型 last 不能與只進遊標一塊兒使用。
使用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:李四
使用遊標對結果集中數據進行更改和刪除
示例:刪除SELECT * FROM szmUser
結果集中的名叫張三的的人,同時將該結果集中名叫李四的名字改成李四四
DECLARE test_cur CURSOR SCROLL FOR SELECT * FROM szmUser OPEN test_cur DECLARE @userId int ,@userName nchar(10) 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指定具體的記錄。
遊標在聲明的時候,能夠定義是靜態遊標仍是動態遊標,遊標默認是動態遊標。
靜態遊標在打開時會將數據集存儲在tempdb中,所以顯示的數據與遊標打開時的數據集保持一致,在遊標打開之後對數據庫的更新不會顯示在遊標中。
動態遊標在打開後會反映對數據庫的更改。全部UPDATE、INSERT 和 DELETE 操做都會顯示在遊標的結果集中,結果集中的行數據值、順序和成員在每次提取時都會改變。
簡而言之:靜態遊標的數據是固定的,不會由於基表的改變而改變;動態遊標的數據是隨着基表變化而變化的。
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=趙六
聲明遊標的時候,默認就是動態遊標,因此這裏咱們只要把上面的代碼中的STATIC
刪除便可,運行結果以下,你好發如今基表中對數據的修改,直接是反應到已聲明的遊標中。咱們修改的id=4的用戶名,直接顯示在遊標的數據中。
2:李四 3:王五 4:測試 --修改基表數據直接做用在已聲明的遊標中 5:Tom 6:Jerry 7:Bob 8:Mark
聲明遊標默認是動態遊標,對基表中數據的改變影響已聲明的動態遊標,不影響已聲明的靜態遊標。
原則是應該儘可能避免使用靜態遊標
動態遊標的打開速度比靜態遊標的打開速度快。當打開靜態遊標時,必須生成內部臨時工做表,而動態遊標則不須要。
在聯接中,靜態遊標的速度可能比動態遊標的速度快。由於動態遊標在滾動時反應對結果集內的各行數據所作的更改,它會消耗資源去檢測基表的更改,所以對於複雜的查詢,且不須要反映基表的更新的遊標的處理應將其定義爲靜態遊標。