MySQL 存儲過程編寫指南

引言

因標籤過多,在實際的應用過程當中,對標籤表的結構進行了變動。git

image.png

從過去的標籤隨意選擇,如今須要對標籤進行分類,簡化選擇難度。添加科目分類以後,須要對歷史上已經被使用過的標籤添加科目信息,進行數據的遷移工做。github

image.png

在數據遷移時,使用到了存儲過程,遇到了諸多問題,特此記錄,分享爬坑過程。sql

存儲過程

存儲過程( Stored Procedure)是一種在數據庫中存儲複雜程序,以便外部程序調用的一種數據庫對象。

存儲過程是爲了完成特定功能的SQL語句集,經編譯建立並保存在數據庫中,用戶可經過指定存儲過程的名字並給定參數(須要時)來調用執行。數據庫

通俗來講,存儲過程就是數據庫中被編譯的SQL函數,和普通函數同樣有函數體,有順序結構、條件結構、循環結構,有參數,有輸入輸出。編程

參考文獻

請先閱讀如下兩篇文章,本文在此基礎上對某些細節作了補充。segmentfault

MYSQL如何對數據進行自動化升級--以若是某數據表存在而且某字段不存在時則執行更新操做爲例 - 河北工業大學夢雲智軟件開發團隊數據結構

數據庫編程實戰 - 鯨冬香函數

標識符替換

因定義存儲過程時;遇到分號會進行語句的執行,須要對;進行轉義。spa

-- 標識符替換
DELIMITER $$

-- 存儲過程定義

-- 從新定義標識符
DELIMITER ;

定義存儲過程

-- 存儲過程
CREATE PROCEDURE procedure_name(IN param BIGINT)
BEGIN

  -- 存儲過程具體實現

END$$

遊標示例

請重點注意如下示例代碼中的順序問題,必定是先聲明變量,再聲明遊標,再處理,不然MySQL會拋錯誤。debug

-- 聲明變量
DECLARE tag_id BIGINT;
DECLARE done INT DEFAULT FALSE;
-- 聲明遊標
DECLARE cur CURSOR FOR SELECT `id` FROM `tag`;
-- 聲明 not found 處理
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

OPEN cur;
FETCH cur INTO tag_id;

WHILE NOT done DO
  -- 遍歷標籤表 逐條遷移數據
  CALL migrate_tag_info_by_id(tag_id);
  FETCH cur INTO tag_id;
END WHILE;

HANDLER

須要重點注意如下代碼:

-- 聲明 not found 處理
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

去查閱相關博客,聲明NOT FOUND的處理器是最經常使用的遊標遍歷方式。

The DECLARE ... HANDLER statement specifies a handler that deals with one or more conditions. If one of these conditions occurs, the specified statement executes. statement can be a simple statement such as SET var_name = value , or a compound statement written using BEGIN and END.

DECLARE ... HANDLER 語句指定了處理單條件或多條件的處理器,若是條件知足時,指定的語句會被執行。語句能夠像SET之類的簡單語句,也能夠是BGGIN ... END代碼塊。

因此當複雜業務場景下,遊標的嵌套會出問題,這裏我選擇多存儲過程的調用來解決。

須要嵌套的時候,可將內層的遊標放到一個新的存儲過程當中,使用CALL進行調用。

WHILE NOT done DO
  -- 遍歷標籤表 逐條遷移數據
  CALL migrate_tag_info_by_id(tag_id);
  FETCH cur INTO tag_id;
END WHILE;

調試方法

編寫存儲過程最難的就是寫了幾十行代碼的存儲過程,執行的時候錯誤不是按行報的,而是一類通用的錯誤,很是難調試。

在查閱相關資料後,MySQL調試存儲過程須要手動進行日誌的打印。

思路以下:

創建logging表,編寫一個名爲debug的存儲過程,功能就是將參數寫入到logging表中。經過打日誌的方式肯定存儲過程執行狀況。

我這裏遇到了須要查詢SELECT語句的中間值的問題,解決思路以下:

根據相關的數據結構創建一張臨時表,經過INSERT INTO SELECT的方式來進行存儲過程執行過程當中相關的中間值的追蹤。

完整示例

完整示例代碼以下:

-- 標識符替換
DELIMITER $$

-- 存儲過程 根據標籤 id 遷移試題對標籤的引用數據
CREATE PROCEDURE migrate_subject_tag_ref_by_id(IN tag_id BIGINT)
BEGIN

  DECLARE tag_name VARCHAR(255);
  DECLARE subject_id BIGINT;
  DECLARE course_id BIGINT;
  DECLARE tag_ref_id BIGINT;
  DECLARE done INT DEFAULT FALSE;

  DECLARE cur CURSOR FOR
  SELECT `subject`.`id`, `subject`.`course_id`
  FROM `subject_tags`
  INNER JOIN `subject`
  ON `subject_tags`.`subjects_id` = `subject`.`id`
  WHERE `subject_tags`.`tags_id` = tag_id;

  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  SELECT `name` INTO tag_name FROM `tag` WHERE `id` = tag_id;

  OPEN cur;
  FETCH cur INTO subject_id, course_id;

  WHILE NOT done DO

    SELECT `id` INTO tag_ref_id FROM `tag`
    WHERE `tag`.`name` = tag_name AND `tag`.`course_id` = course_id;

    UPDATE `subject_tags` SET `tags_id` = tag_ref_id
    WHERE `subjects_id` = subject_id AND `tags_id` = tag_id;

    FETCH cur INTO subject_id, course_id;
  END WHILE;

END$$

-- 存儲過程 根據標籤 id 遷移新標籤數據
CREATE PROCEDURE migrate_tag_info_by_id(IN tag_id BIGINT)
BEGIN

  DECLARE tag_name VARCHAR(255);
  DECLARE course_id BIGINT;
  DECLARE done INT DEFAULT FALSE;

  DECLARE cur CURSOR FOR
  SELECT `subject`.`course_id`
  FROM `subject_tags`
  INNER JOIN `subject`
  ON `subject_tags`.`subjects_id` = `subject`.`id`
  WHERE `subject_tags`.`tags_id` = tag_id
  GROUP BY `subject`.`course_id`;

  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  SELECT `name` INTO tag_name FROM `tag` WHERE `id` = tag_id;

  OPEN cur;
  FETCH cur INTO course_id;

  WHILE NOT done DO
    -- 建立新標籤
    INSERT INTO `tag`(`name`, `course_id`) VALUES(tag_name, course_id);
    FETCH cur INTO course_id;
  END WHILE;

  -- 遷移對歷史標籤的引用
  CALL migrate_subject_tag_ref_by_id(tag_id);
  -- 刪除歷史標籤
  DELETE FROM `tag` WHERE `tag`.`id` = tag_id;

END$$

-- 存儲過程 遷移全部標籤數據
CREATE PROCEDURE migrate_all_tag_info()
BEGIN

  DECLARE tag_id BIGINT;
  DECLARE done INT DEFAULT FALSE;
  DECLARE cur CURSOR FOR SELECT `id` FROM `tag`;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  OPEN cur;
  FETCH cur INTO tag_id;

  WHILE NOT done DO
    -- 遍歷標籤表 逐條遷移數據
    CALL migrate_tag_info_by_id(tag_id);
    FETCH cur INTO tag_id;
  END WHILE;

END$$
-- 從新定義標識符
DELIMITER ;

CALL migrate_all_tag_info();

版權聲明

本文做者: 河北工業大學夢雲智開發團隊 - 張喜碩
相關文章
相關標籤/搜索