T-SQL——游標


2020年5月27日 21:10:00

1.游標簡介

1.0 理解定義

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

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

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

怎么理解“為了處理查詢的結果集中特定行的數據,我們使用游標處理”? 其實,游標的英文單詞是cursor,也可以翻譯為光標,其實類比我們編輯文檔,當想要編輯具體的某一行的時候,我們需要使用光標移到該行進行編輯,在SQL中游標的作用是一樣的。

當然,本質上就是個定義在結果集上的指針,我們可以控制該指針遍歷結果集。

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

1.1 游標的主要作用

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

1.2 游標的優缺點

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

1.3 游標生命周期

游標的生命周期:

  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 ] ] ]

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

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


4.參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM