MySQLストアドプロシージャでのカーソルを利用した結果セットのループ処理

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

タグ: MySQL ストアドプロシージャ カーソル SQL

7月20日 16:01 投稿