SQL必知必會(游標)

什么是游標?

在數(shù)據(jù)庫中,游標是個重要的概念,它提供了一種靈活的操作方式,可以讓我們從數(shù)據(jù)結(jié)果集中每次提取一條數(shù)據(jù)記錄進行操作。游標讓 SQL 這種面向集合的語言有了面向過程開發(fā)的能力??梢哉f,游標是面向過程的編程方式,這與面向集合的編程方式有所不同。

在 SQL 中,游標是一種臨時的數(shù)據(jù)庫對象,可以指向存儲在數(shù)據(jù)庫表中的數(shù)據(jù)行指針。這里游標充當了指針的作用,我們可以通過操作游標來對數(shù)據(jù)行進行操作。

比如我們查詢了 heros 數(shù)據(jù)表中最大生命值大于 8500 的英雄都有哪些:

SELECT id, name, hp_max FROM heros WHERE hp_max > 8500;

如何使用游標?

游標實際上是一種控制數(shù)據(jù)集的更加靈活的處理方式。

如果我們想要使用游標,一般需要經(jīng)歷五個步驟。不同 DBMS 中,使用游標的語法可能略有不同。

第一步,定義游標。

DECLARE cursor_name CURSOR FOR select_statement

這個語法適用于 MySQL,SQL Server,DB2 和 MariaDB。如果是用 Oracle 或者 PostgreSQL,需要寫成:

DECLARE cursor_name CURSOR IS select_statement

要使用 SELECT 語句來獲取數(shù)據(jù)結(jié)果集,而此時還沒有開始遍歷數(shù)據(jù),這里 select_statement 代表的是 SELECT 語句。

第二步,打開游標。

OPEN cursor_name

當我們定義好游標之后,如果想要使用游標,必須先打開游標。打開游標的時候 SELECT 語句的查詢結(jié)果集就會送到游標工作區(qū)。

第三步,從游標中取得數(shù)據(jù)。

FETCH cursor_name INTO var_name ...

這句的作用是使用 cursor_name 這個游標來讀取當前行,并且將數(shù)據(jù)保存到 var_name 這個變量中,游標指針指到下一行。如果游標讀取的數(shù)據(jù)行有多個列名,則在 INTO 關(guān)鍵字后面賦值給多個變量名即可。

第四步,關(guān)閉游標。

CLOSE cursor_name

有 OPEN 就會有 CLOSE,也就是打開和關(guān)閉游標。當我們使用完游標后需要關(guān)閉掉該游標。關(guān)閉游標之后,我們就不能再檢索查詢結(jié)果中的數(shù)據(jù)行,如果需要檢索只能再次打開游標。

最后一步,釋放游標。

DEALLOCATE PREPARE

有 DECLARE 就需要有 DEALLOCATE,DEALLOCATE 的作用是釋放游標。我們一定要養(yǎng)成釋放游標的習慣,否則游標會一直存在于內(nèi)存中,直到進程結(jié)束后才會自動釋放。當你不需要使用游標的時候,釋放游標可以減少資源浪費。

我先創(chuàng)建一個存儲過程 calc_hp_max,然后在存儲過程中定義游標 cur_hero,使用 FETCH 獲取每一行的具體數(shù)值,然后賦值給變量 hp,再用變量 hp_sum 做累加求和,最后再輸出 hp_sum,代碼如下:

CREATE PROCEDURE `calc_hp_max`()
BEGIN
       -- 創(chuàng)建接收游標的變量
       DECLARE hp INT;  
       -- 創(chuàng)建總數(shù)變量 
       DECLARE hp_sum INT DEFAULT 0;
       -- 創(chuàng)建結(jié)束標志變量  
       DECLARE done INT DEFAULT false;
       -- 定義游標     
       DECLARE cur_hero CURSOR FOR SELECT hp_max FROM heros;
       
       OPEN cur_hero;
       read_loop:LOOP 
       FETCH cur_hero INTO hp;
       SET hp_sum = hp_sum + hp;
       END LOOP;
       CLOSE cur_hero;
       SELECT hp_sum;
       DEALLOCATE PREPARE cur_hero;
END

你會發(fā)現(xiàn)執(zhí)行call calc_hp_max()這一句的時候系統(tǒng)會提示 1329 錯誤,也就是在 LOOP 中當游標沒有取到數(shù)據(jù)時會報的錯誤。

當游標溢出時(也就是當游標指向到最后一行數(shù)據(jù)后繼續(xù)執(zhí)行會報的錯誤),我們可以定義一個 continue 的事件,指定這個事件發(fā)生時修改變量 done 的值,以此來判斷游標是否已經(jīng)溢出,即:

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;  

同時在循環(huán)中我們需要加上對 done 的判斷,如果游標的循環(huán)已經(jīng)結(jié)束,就需要跳出 read_loop 循環(huán),完善的代碼如下:

CREATE PROCEDURE `calc_hp_max`()
BEGIN
       -- 創(chuàng)建接收游標的變量
       DECLARE hp INT;  
 
       -- 創(chuàng)建總數(shù)變量 
       DECLARE hp_sum INT DEFAULT 0;
       -- 創(chuàng)建結(jié)束標志變量  
     DECLARE done INT DEFAULT false;
       -- 定義游標     
       DECLARE cur_hero CURSOR FOR SELECT hp_max FROM heros;
       -- 指定游標循環(huán)結(jié)束時的返回值  
     DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;  
       
       OPEN cur_hero;
       read_loop:LOOP 
       FETCH cur_hero INTO hp;
       -- 判斷游標的循環(huán)是否結(jié)束  
       IF done THEN  
                     LEAVE read_loop;
       END IF; 
              
       SET hp_sum = hp_sum + hp;
       END LOOP;
       CLOSE cur_hero;
       SELECT hp_sum;
       DEALLOCATE PREPARE cur_hero;
END

在游標中的循環(huán)中,除了使用 LOOP 循環(huán)以外,你還可以使用 REPEAT… UNTIL…以及 WHILE 循環(huán)。它們同樣需要設(shè)置 CONTINUE 事件來處理游標溢出的情況。

所以你能看出,使用游標可以讓我們對 SELECT 結(jié)果集中的每一行數(shù)據(jù)進行相同或者不同的操作,從而很精細化地管理結(jié)果集中的每一條數(shù)據(jù)。

使用游標來解決一些常見的問題

我剛才講了一個簡單的使用案例,實際上如果想要統(tǒng)計 hp_sum,完全可以通過 SQL 語句來完成,比如:

SELECT SUM(hp_max) FROM heros;

那么游標都有什么用呢?

當你需要處理一些復雜的數(shù)據(jù)行計算的時候,游標就會起到作用了。我舉個例子,還是針對 heros 數(shù)據(jù)表,假設(shè)我們想要對英雄的物攻成長(對應(yīng) attack_growth)進行升級,在新版本中大范圍提升英雄的物攻成長數(shù)值,但是針對不同的英雄情況,提升的幅度也不同,具體提升的方式如下。

如果這個英雄原有的物攻成長小于 5,那么將在原有基礎(chǔ)上提升 7%-10%。如果物攻成長的提升空間(即最高物攻 attack_max- 初始物攻 attack_start)大于 200,那么在原有的基礎(chǔ)上提升 10%;如果物攻成長的提升空間在 150 到 200 之間,則提升 8%;如果物攻成長的提升空間不足 150,則提升 7%。

如果原有英雄的物攻成長在 5—10 之間,那么將在原有基礎(chǔ)上提升 5%。

如果原有英雄的物攻成長大于 10,則保持不變。

以上所有的更新后的物攻成長數(shù)值,都需要保留小數(shù)點后 3 位。

你能看到上面這個計算的情況相對復雜,實際工作中你可能會遇到比這個更加復雜的情況,這時你可以采用面向過程的思考方式來完成這種任務(wù),也就是說先取出每行的數(shù)值,然后針對數(shù)值的不同情況采取不同的計算方式。

針對上面這個情況,你自己可以用游標來完成轉(zhuǎn)換,具體的代碼如下:

CREATE DEFINER=`root`@`localhost` PROCEDURE `alter_attack_growth`()
BEGIN
       -- 創(chuàng)建接收游標的變量
       DECLARE temp_id INT;  
       DECLARE temp_growth, temp_max, temp_start, temp_diff FLOAT;  
 
       -- 創(chuàng)建結(jié)束標志變量  
       DECLARE done INT DEFAULT false;
       -- 定義游標     
       DECLARE cur_hero CURSOR FOR SELECT id, attack_growth, attack_max, attack_start FROM heros;
       -- 指定游標循環(huán)結(jié)束時的返回值  
       DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;  
       
       OPEN cur_hero;  
       FETCH cur_hero INTO temp_id, temp_growth, temp_max, temp_start;
       REPEAT
                     IF NOT done THEN
                            SET temp_diff = temp_max - temp_start;
                            IF temp_growth < 5 THEN
                                   IF temp_diff > 200 THEN
                                          SET temp_growth = temp_growth * 1.1;
                                   ELSEIF temp_diff >= 150 AND temp_diff <=200 THEN
                                          SET temp_growth = temp_growth * 1.08;
                                   ELSEIF temp_diff < 150 THEN
                                          SET temp_growth = temp_growth * 1.07;
                                   END IF;                       
                            ELSEIF temp_growth >=5 AND temp_growth <=10 THEN
                                   SET temp_growth = temp_growth * 1.05;
                            END IF;
                            UPDATE heros SET attack_growth = ROUND(temp_growth,3) WHERE id = temp_id;
                     END IF;
       FETCH cur_hero INTO temp_id, temp_growth, temp_max, temp_start;
       UNTIL done = true END REPEAT;
             
       
       CLOSE cur_hero;
       -- DEALLOCATE PREPARE cur_hero;
END

這里我創(chuàng)建了 alter_attack_growth 這個存儲過程,使用了 REPEAT…UNTIL…的循環(huán)方式,針對不同的情況計算了新的物攻成長 temp_growth,然后對原有的 attack_growth 進行了更新,最后調(diào)用 call alter_attack_growth(); 執(zhí)行存儲過程。

有一點需要注意的是,我們在對數(shù)據(jù)表進行更新前,需要備份之前的表,我們可以將備份后的表命名為 heros_copy1。更新完 heros 數(shù)據(jù)表之后,你可以看下兩張表在 attack_growth 字段上的對比,我們使用 SQL 進行查詢:

SELECT heros.id, heros.attack_growth, heros_copy1.attack_growth FROM heros JOIN heros_copy1 WHERE heros.id = heros_copy1.id;

通過前后兩張表的 attack_growth 對比你也能看出來,存儲過程通過游標對不同的數(shù)據(jù)行進行了更新。

需要說明的是,以上代碼適用于 MySQL,如果在 SQL Server 或 Oracle 中,使用方式會有些差別。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是游標? 在數(shù)據(jù)庫中,游標是個重要的概念,它提供了一種靈活的操作方式,可以讓我們從數(shù)據(jù)結(jié)果集中每次提取一條數(shù)據(jù)...
    顧子豪閱讀 3,568評論 2 6
  • 常用的 SQL 函數(shù)有哪些 一、算術(shù)函數(shù) 算術(shù)函數(shù),顧名思義就是對數(shù)值類型的字段進行算術(shù)運算。常用的算術(shù)函數(shù)及含義...
    羋學僧閱讀 887評論 1 2
  • 一、什么是存儲過程,如何創(chuàng)建一個存儲過程 存儲過程的英文是 Stored Procedure。它的思想很簡單,就是...
    羋學僧閱讀 1,152評論 1 5
  • SELECT 查詢的基礎(chǔ)語法 創(chuàng)建了一個王者榮耀英雄數(shù)據(jù)表,這張表里一共有 69 個英雄,23 個屬性值(不包括英...
    羋學僧閱讀 819評論 0 2
  • 今天青石的票圈出鏡率最高的,莫過于張藝謀的新片終于定檔了。 一張滿溢著水墨風的海報一次次的出現(xiàn)在票圈里,也就是老謀...
    青石電影閱讀 10,809評論 1 2

友情鏈接更多精彩內(nèi)容