SQL Server 創建游標(cursor)


游標的定義:

游標則是處理結果集的一種機制,它可以定位到結果集中的某一行,也可以移動游標定位到你所需要的行中進行操作數據。與 select 語句的不同是,select 語句面向的是結果集,游標面向的是結果集的行。 游標其實可以理解成一個定義在特定數據集上的指針,我們可以控制這個指針遍歷數據集,或者僅僅是指向特定的行。

 

游標的分類:

靜態游標(static):當游標被建立時,將會創建 FOR 后面的 SELECT 語句所包含數據集的副本存入 tempdb 數據庫中,任何對於底層表內數據的更改不會影響到游標的內容。

即打開游標之后,對游標查詢的數據表的數據進行增刪改操做之后,靜態游標中 select 的數據依舊顯示的為沒有操作之前的數據。

如果想與操作之后的數據一致,則關閉之后重新打開游標即可。

動態游標(dynamic):動態游標與靜態游標相反,當底層數據表的數據更改時,游標的內容也隨之得到反映,在下一次 fetch 中, 行的數據值、順序和成員身份在每次提取時都會更改。

只進游標(fast_forward):只進游標不支持滾動,只支持從頭到尾按順序讀取數據,對數據執行增刪改操作,在提取時是可見的,但由於該游標只能進不能向后滾動,所以在行提取后對行做增刪改是不可見的。

鍵集游標(keyset):打開鍵集驅動游標時,結果集的每行數據被一組唯一標識符進行標識,被標識的列做刪改時,用戶滾動游標是可見的,其他用戶執行的插入是不可見的(不能通過 Transact-SQL 服務器游標執行插入)。如果刪除了某行,嘗試讀取的行返回 @@FETCH_STATUS為-2。 從游標外部更新鍵值類似於刪除舊行后再插入新行。 具有新值的行不可見,並且嘗試提取具有舊值的行返回 @@FETCH_STATUS為-2。如果通過指定 WHERE CURRENT OF 子句來通過游標執行更新,則新值可見。

 

游標的生命周期:

游標的生命周期包含有五個階段:聲明游標、打開游標、讀取游標數據、關閉游標、釋放游標。

 

語法:

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

參數:

cursor_name:游標的名稱

Local:局部游標,只在定義它的批處理,存儲過程或觸發器中有效。

Global:全局游標,在由此連接執行的任何存儲過程或批處理中,都可以引用該游標。該游標僅在斷開連接時隱式釋放。

如果未指定游標作用域,那么默認為全局游標。

Forward_Only:指定游標只能從第一行滾動到最后一行。 FETCH NEXT 是唯一支持的提取選項。

Scroll:指定游標在定義的數據集中可以向任何方向,或任何位置移動。

如果在指定 FORWARD_ONLY 時不指定 STATIC、KEYSET 和 DYNAMIC 關鍵字,則游標默認為 DYNAMIC 游標進行操作。

如果 FORWARD_ONLY 和 SCROLL 均未指定,那么除非指定了 STATIC、KEYSET 或 DYNAMIC 關鍵字,否則默認值為 FORWARD_ONLY。

STATIC、KEYSET 和 DYNAMIC 游標默認為 SCROLL。

Static:指定為靜態游標

KeySet:指定為鍵集游標

Dynamic:指定為動態游標,動態游標不支持 ABSOLUTE 提取選項。

Fast_Forward:指定為啟用了性能優化的 FORWARD_ONLY、READ_ONLY 游標。 如果指定了 SCROLL 或 FOR_UPDATE,則不能同時指定 FAST_FORWARD。

Read_Only:只讀,即不能通過游標對數據進行更新操作。

Scroll_Locks:將讀入游標的所有數據進行鎖定,防止其他程序進行更改,以確保更新的絕對成功。如果還指定了FAST_FORWARD或STATIC,則不能指定SCROLL_LOCKS。

Optimistic:不鎖定任何數據,當需要在游標中更新數據時,如果底層表數據更新,則游標內數據更新不成功,如果底層表數據未更新,則游標內表數據可以更新。如果指定了 Fast_Forward ,則不能指定它。

Type_Warning:指定如果游標從所請求的類型隱式轉換為另一種類型,則向客戶端發送警告消息。

select_statement:定義游標結果集的標准 SELECT 語句。

For Update[of column_name ,....]:定義游標中可更新的列。如果指定了 UPDATE,也指定了列,僅指定的列進行修改。如果指定了 UPDATE,但未指定列,則除非指定了 READ_ONLY 並發選項,否則可以更新所有的列。

 

在聲明游標后,可使用下列系統存儲過程確定游標的特性:

 

定義一個局部動態的游標:

-- 定義一個局部動態游標
declare test_cursor cursor local 
scroll dynamic optimistic
for select S_Id,S_StuNo,S_Name,S_Sex,S_Height from Student where C_S_Id='2'

打開游標:

-- 打開游標語法
open [ Global ] cursor_name | cursor_variable_name

cursor_name:定義的游標名稱。

cursor_variable_name:游標變量名稱,即引用了游標的變量的名稱。

-- 打開游標
open test_cursor

-- 打開局部游標
open local test_cursor

-- 打開全局游標
open global test_cursor

PS:如果未指定 local 和 global,優先打開局部游標,如果沒有這個局部游標,則再打開全局游標。也就是說,當全局游標和局部游標重名時,默認會打開局部游標。

提取數據:

-- 游標提取數據語法
FETCH   
          [ [ NEXT | PRIOR | FIRST | LAST   
                    | ABSOLUTE { n | @nvar }   
                    | RELATIVE { n | @nvar }   
               ]   
               FROM   
          ]   
{ { [ GLOBAL ] cursor_name } | @cursor_variable_name }   
[ INTO @variable_name [ ,...n ] ]  

Frist:結果集的第一行

Prior:當前位置的上一行

Next:當前位置的下一行

Last:最后一行

Absolute:直接跳到指定的行(n)

Relative:相對於當前位置跳指定的行數(n)

Into @variable_name[,...]:將提取的數據存入到變量中,@variable_name 為變量名。

fetch first from test_cursor into @Name
select @Name
fetch prior from test_cursor into @Name
select @Name
fetch next from test_cursor into @Name
select @Name
fetch last from test_cursor into @Name
select @Name
fetch absolute 1 from test_cursor into @Name
select @Name
fetch relative 1 from test_cursor into @Name
select @Name

PS:動態游標不支持 ABSOLUTE 提取選項,對於未指定 SCROLL 選項的游標來說,只支持 NEXT 取值。

 

游標經常會和全局變量 @@FETCH_STATUS 與 WHILE 循環來共同使用,以達到遍歷游標所在數據集的目的。

@@FETCH_STATUS 有三種值:

0:Fetch 語句成功。

-1:Fetch 語句失敗或行不在結果集中。

-2:提取的行不存在。

使用系統全局變量 @@cursor_rows 查詢游標中結果集中的行數:

n:表示返回的實際行數。

-1:表示游標是動態的。

0:表示空集游標。

 

使用游標刪除、修改數據:

--使用游標修改當前數據語法
update 基表名 Set 列名=[,...] where current of 游標名

update Student set S_Name='233' where current of test_cursor

--使用游標刪除當前數據語法
delete 基表名  where current of 游標名

delete Student where current of test_cursor

關閉游標:

--關閉游標語法
close [ Global ] cursor_name | cursor_variable_name
--關閉游標
close test_cursor

--關閉局部游標
close local test_cursor

--關閉全局游標
close global test_cursor

釋放游標:

--釋放游標語法
deallocate  [ Global ] cursor_name | cursor_variable_name
--釋放游標
deallocate test_cursor

--釋放局部游標
deallocate local test_cursor

--釋放全局游標
deallocate global test_cursor

 

下面是一個完整的例子:

下面來定義一個游標用來修改每一個學生的學號,在學號前面添加一位 1。

declare @StuNo nvarchar(50)
declare test_cursor cursor local 
for select S_StuNo from Student
open test_cursor 
fetch next from test_cursor into @StuNo
while(@@FETCH_STATUS=0)
begin
    set @StuNo='1'+@StuNo
    if(@StuNo is not null and @StuNo <> '')
    begin
        update Student set S_StuNo=@StuNo where current of test_cursor
    end
    fetch next from test_cursor into @StuNo
end
close test_cursor
deallocate test_cursor

select * from Student

如果只是用於循環之類的,最好不要使用游標,游標是不好的,反正能不使用就盡量不用游標,改用其他方法實現。

比如上面的例子,我們也可以用自定義循環來實現,如下:

declare @I    int
declare @Num int
declare @SID int
declare @StuNo nvarchar(50)

select @Num=COUNT(1) from Student

if(@Num>0)
begin
    set @I=0
    while(@I<@Num)
    begin
        set @I=@I+1

        select @StuNo=t.S_StuNo,@SID=t.S_Id from 
            (select S_Id,S_StuNo,ROW_NUMBER() over(order by S_StuNo) RowNum from Student) t
        where t.RowNum=@I

        set @StuNo=SUBSTRING(@StuNo,2,len(@StuNo)-1)

        update Student set S_StuNo=@StuNo where S_Id=@SID
    end
end

select * from Student

把上面改掉的學生的學號又給改回來了,效果是一樣的。當然,這樣一條一條的改明顯是有點欠扁的。。。不過只是一個循環的例子。

當然,存在就有它的意義,游標也有它的好處,比如用自定義的循環解決不了的問題,它就能排上用場了。比如下面一個例子,當我選擇一個父節點的時候,我要刪除它下面的所有子孫節點(不管多少層級)。

演示數據:

  declare @NodeId int
  declare @NID int
  set @NodeId=1        -- 表示選擇節點的ID

  declare @temp_value table
  (
      ID int identity(1,1),
      value int
  )

  insert into @temp_value 
  select D_ID from Department where D_ID=@NodeId
  
  declare one_curr cursor local scroll dynamic        --定義一個局部的動態游標
  for select value from @temp_value

  open one_curr
  fetch next from one_curr into @NID
  while(@@FETCH_STATUS=0)
  begin
        if exists(select D_ID from Department where D_ParentID=@NID)    --判斷是否存在子節點
        begin
            insert into @temp_value 
            select D_ID from Department where D_ParentID=@NID        --存在就把所有的子節點的ID插入表變量,后面循環使用
        end
        
        delete from Department where D_ID=@NID        --刪除相應的節點

        fetch next from one_curr into @NID
  end
  close one_curr
  deallocate one_curr

  select * from Department

 

在這里容許我裝個逼:游標永遠只是你最后無奈之下的選擇,而不是首選!!!

 

參考:

http://www.cnblogs.com/moss_tan_jun/archive/2011/11/26/2263988.html#!comments

http://www.cnblogs.com/knowledgesea/p/3699851.html

https://msdn.microsoft.com/zh-cn/library/ms180169.aspx


免責聲明!

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



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