MySQLのバージョンによって、ストアドプロシージャやカーソルの構文が微妙に異なる場合があるため、実装時は対象バージョンの公式リファレンスを確認することが重要である。
クエリ結果の各行に対して順次処理を行う場合、カーソル(CURSOR)を利用して結果セットをループで処理する。基本の手順は以下の通りである:
- ループ内で使用する変数の宣言
- カーソルの宣言
- データ終了時のフラグ設定 (CONTINUE HANDLER)
- カーソルのオープン
- FETCHによるデータ取得とLOOP内での処理
- カーソルのクローズ
以下に、具体的な実装例を示す。
単一テーブルの更新処理
特定の条件で抽出したレコードの値を用いて、別のカラムを更新する例。
CREATE PROCEDURE update_inactive_accounts()
BEGIN
DECLARE v_finished INT DEFAULT 0;
DECLARE p_uid INT;
DECLARE p_account_name VARCHAR(100);
DECLARE p_status TINYINT;
-- カーソルの宣言
DECLARE account_cursor CURSOR FOR
SELECT id, account_name, status FROM sys_accounts WHERE status = 0;
-- データが見つからない場合の終了フラグ設定
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_finished = 1;
OPEN account_cursor;
process_loop: LOOP
FETCH account_cursor INTO p_uid, p_account_name, p_status;
IF v_finished = 1 THEN
LEAVE process_loop;
END IF;
-- 取得したIDを用いてステータスを更新
UPDATE sys_accounts
SET status = 1
WHERE id = p_uid;
END LOOP process_loop;
CLOSE account_cursor;
END
複数カーソルを使用した条件付き挿入
2つの異なるテーブルから値を取得し、条件に応じて別テーブルにデータを挿入する例。
CREATE PROCEDURE sync_and_insert_data()
BEGIN
DECLARE v_done INT DEFAULT 0;
DECLARE v_ref_id INT;
DECLARE v_val_a INT;
DECLARE v_val_b INT;
DECLARE main_cursor CURSOR FOR SELECT source_id, value_a FROM master_data;
DECLARE sub_cursor CURSOR FOR SELECT value_b FROM sub_data;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
OPEN main_cursor;
OPEN sub_cursor;
iterate_loop: LOOP
FETCH main_cursor INTO v_ref_id, v_val_a;
FETCH sub_cursor INTO v_val_b;
IF v_done = 1 THEN
LEAVE iterate_loop;
END IF;
IF v_val_a > v_val_b THEN
INSERT INTO result_log (ref_id, stored_value) VALUES (v_ref_id, v_val_a);
ELSE
INSERT INTO result_log (ref_id, stored_value) VALUES (v_ref_id, v_val_b);
END IF;
END LOOP;
CLOSE main_cursor;
CLOSE sub_cursor;
END
重複排除後のレコード挿入
特定のカラムで重複を排除した結果セットをループし、別テーブルへレコードを新規挿入する例。
CREATE PROCEDURE grant_initial_bonus()
BEGIN
DECLARE v_end INT DEFAULT 0;
DECLARE v_customer_id INT;
-- 重複を排除した顧客IDを取得するカーソル
DECLARE customer_cursor CURSOR FOR
SELECT DISTINCT customer_id FROM order_history;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_end = 1;
OPEN customer_cursor;
insert_loop: LOOP
FETCH customer_cursor INTO v_customer_id;
IF v_end = 1 THEN
LEAVE insert_loop;
END IF;
-- 別テーブルへの登録処理
INSERT INTO bonus_points (customer_id, points, created_at, is_active)
VALUES (v_customer_id, 500, NOW(), 1);
END LOOP insert_loop;
CLOSE customer_cursor;
END