存儲過程——遊標

2020年5月27日 21:10:00

1. 遊標簡介

1.0 理解定義

SQL遊標(cursor)是一個數據庫對象,用於從結果集中檢索某一行的數據。html

遊標是系統爲用戶開設的一個數據緩衝區,存放SQL語句的執行結果。每一個遊標區都有一個名字,用戶能夠用SQL語句逐一從遊標中獲取記錄,並賦給主變量,交由主語言進一步處理。sql

在編程中,咱們使用諸如forwhile之類的循環一次遍歷一項,遊標遵循相同的方法。當在SQL中,應用程序邏輯須要一次只處理一行,而不是一次處理整個結果集。可使用遊標完成此操做。數據庫

怎麼理解「爲了處理查詢的結果集中特定行的數據,咱們使用遊標處理」? 其實,遊標的英文單詞是cursor,也能夠翻譯爲光標,其實類比咱們編輯文檔,當想要編輯具體的某一行的時候,咱們須要使用光標移到該行進行編輯,在SQL中游標的做用是同樣的。編程

固然,本質上就是個定義在結果集上的指針,咱們能夠控制該指針遍歷結果集。編程語言

這裏補充一下:理論上SQL編寫是按照面向集合的思惟模式,而咱們使用遊標則又回到了面向過程的思惟模式。此中思想非三言二語可說明白的,相關知識能夠參考《SQL進階教程》2.6章節!函數

1.1 遊標的主要做用

  1. 定位到結果集中的某一行。
  2. 對當前位置的數據進行讀寫。
  3. 能夠對結果集中的數據單獨操做,而不是整行執行相同的操做。
  4. 是面向集合的數據庫管理系統和麪向行的程序設計之間的橋樑。

1.2 遊標的優缺點

  1. 優勢:參考上文中游標的做用
  2. 缺點:濫用遊標會影響系統性能。
    通常來講,有一個共識:能不用遊標就不要用遊標
    事實上,編寫SQL語句的時候大多數的情形下是沒有必要使用遊標的。

1.3 遊標生命週期

遊標的生命週期:sqlserver

  1. 聲明遊標(Declare Cursor)
  2. 打開遊標(Open Cursor)
  3. 提取遊標(Fetch Cursor)
  4. 關閉遊標(Close Cursor)
  5. 釋放遊標(Deallocate Cursor)

使用遊標的過程以下:性能

遊標生命週期

注:圖片來源 https://www.sqlservertutorial.net/sql-server-stored-procedures/sql-server-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 ] ] ]

【說明】方括號中的關鍵之是可選的,具體做用以下:fetch

  1. 做用域

    • Local:遊標做用域爲局部,只在定義它的批處理、函數和存儲過程當中有效。
    • Global:遊標做用域爲全局,由鏈接執行的任何存儲過程或批處理中,均可以引用該遊標。
    • 默認值是Local
  2. 遊標方向

    • Forward_Only:指定遊標智能從第一行滾到最後一行,種遊標稱爲:只進遊標
    • Scroll:指定遊標在定義的數據集中向任何方向,或任何位置移動。
    • 默認是Forward_Only
  3. 遊標讀取的數據和基表數據關係

    • Static代表:遊標一旦指定了select查詢出的結果集,以後任何對於基表(即:select語句所查詢的表)內數據的更改不會影響到遊標的內容。該種遊標稱爲靜態遊標

    • Dynamic和Static徹底相反的選項,當底層數據庫更改時,遊標的內容也隨之獲得反映,在下一次fetch中,數據內容會隨之改變。該種遊標稱爲動態遊標

    • KeySet:指明當再遊標被打開時遊標中的列的順序時固定的,遊標只維持其所依賴的基表的鍵

    • Fast_Forward:指明一個Forward_Only且Read_Only型遊標。注意:一旦聲明瞭Fast_Forward,則以前就不能夠選擇Scroll類型的遊標。一樣,在以後也就不能使用Scroll_Locks和Optimistic選項

    • 默認值是Dynamic

  4. 遊標是否鎖定數據

    • Read_Only意味着聲明的遊標只能讀取數據,遊標不能作任何更新操做

    • Scroll_Locks是另外一種極端,將讀入遊標的全部數據進行鎖定,防止其餘程序進行更改,以確保更新的絕對成功

    • Optimistic是相對比較好的一個選擇,不鎖定任何數據,當須要在遊標中更新數據時,若是底層表數據更新,則遊標內數據更新或刪除會不成功,若是,底層表數據未更新,則遊標內表數據能夠更新或刪除

  5. Type_Warning:指明若遊標類型被修改爲與用戶定義的類型不一樣時,將發送一個警告信息給客戶端。

  6. Update[Of colunm_name[,...n]]:定義利用遊標可更新的列。若果列出了Of colunm_name[,...n],則只容許修改列出的列

  7. 其實,從上面能夠看出遊標的聲明是有許多的可選項。
    可是通常來講,只要記住遊標聲明的默認值。通常實際開發中,如無必要則使用默認值便可。

②打開遊標

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 NEXTQ提取數據,如果使用其餘的提取數據的方式則會報錯,好比使用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	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指定具體的記錄。

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定義遊標


4. 參考

相關文章
相關標籤/搜索