MySQL | SQLite経験者向け、30日で習得するMySQL:実務SQL力 - Day12 ストアドプロシージャ②

SQL MySQL
スポンサーリンク

Day12 前半のゴール

「“SQLの中でプログラムを書く”感覚をつかむ」

Day11 でストアドプロシージャの基本構文と、
IN / OUT パラメータ、IF・CASE の入口まで触れました。

Day12 のテーマは、その続きとしての

条件分岐をもう少し“ちゃんと”使う
ループ(繰り返し)の基本形を知る

です。

前半のゴールはこうです。

IF / CASE を「読める・書ける」レベルにする
WHILE を使ったシンプルなループのイメージを持つ
「SQLの世界でも“普通のプログラム”が書ける」と実感する

ここまで行けば、後半でカーソルやもう少し実務寄りのループに入っていけます。


条件分岐をもう一段整理する

「IF は“フローを分ける”、CASE は“値で分ける”」

まずは条件分岐から、もう一度整理します。

IF は「処理の流れ」を分けるためのものです。

IF 条件 THEN
  処理A;
ELSE
  処理B;
END IF;
SQL

これは、アプリの if 文とほぼ同じ感覚で使えます。

一方、CASE は「ある値に応じて結果を変える」ためのものです。

SET v_rank = CASE
  WHEN v_total >= 100000 THEN 'GOLD'
  WHEN v_total >= 50000  THEN 'SILVER'
  ELSE 'BRONZE'
END;
SQL

IF でも書けますが、
「1つの値を、条件に応じて別の値に変換する」パターンは
CASE の方が読みやすくなりやすいです。

ここで大事なのは、

IF は“フロー制御”
CASE は“値の変換”

というイメージを持つことです。


例:ユーザーの状態に応じてメッセージを変える

「IF で“処理の分岐”を体感する」

次のような users テーブルを想像します。

CREATE TABLE users (
  id             INT AUTO_INCREMENT PRIMARY KEY,
  name           VARCHAR(50)  NOT NULL,
  is_deleted     TINYINT(1)   NOT NULL DEFAULT 0,
  email_verified TINYINT(1)   NOT NULL DEFAULT 0
) ENGINE=InnoDB;
SQL

このユーザーに対して、

退会済みなら「DELETED」
メール未確認なら「NEED_VERIFY」
それ以外は「ACTIVE」

というステータス文字列を返すプロシージャを作ってみます。

DELIMITER //

CREATE PROCEDURE get_user_status(
  IN  p_user_id INT,
  OUT p_status  VARCHAR(20)
)
BEGIN
  DECLARE v_is_deleted     TINYINT(1);
  DECLARE v_email_verified TINYINT(1);

  SELECT
    is_deleted,
    email_verified
  INTO
    v_is_deleted,
    v_email_verified
  FROM users
  WHERE id = p_user_id;

  IF v_is_deleted = 1 THEN
    SET p_status = 'DELETED';
  ELSEIF v_email_verified = 0 THEN
    SET p_status = 'NEED_VERIFY';
  ELSE
    SET p_status = 'ACTIVE';
  END IF;
END //

DELIMITER ;
SQL

ここでやっていることは、完全に「普通のプログラム」です。

ユーザーの状態を変数に読み込む
IF で条件をチェックして、ステータス文字列を決める

SQLだけを書いていたときと違って、
「DBの中でロジックが動いている」感覚が出てきたと思います。


ループの基本:WHILE

「“条件が成り立つ間、繰り返す”という一番素直な形」

次に、ループに入ります。

MySQL のストアドプロシージャには、
いくつかループ構文がありますが、
一番素直で分かりやすいのが WHILE です。

基本形はこうです。

WHILE 条件 DO
  処理;
END WHILE;
SQL

アプリの while 文と同じで、

条件が真の間、処理を繰り返す
処理の中で条件が変わっていき、いつか偽になってループを抜ける

という動きです。


例:1〜Nまでの合計を計算する

「“ループの形”だけに集中する練習」

まずは、あえてテーブルを使わない例で、
ループの形だけに集中してみます。

「1 から N までの合計を計算する」プロシージャです。

DELIMITER //

CREATE PROCEDURE sum_to_n(
  IN  p_n     INT,
  OUT p_total INT
)
BEGIN
  DECLARE v_i     INT;
  DECLARE v_sum   INT;

  SET v_i   = 1;
  SET v_sum = 0;

  WHILE v_i <= p_n DO
    SET v_sum = v_sum + v_i;
    SET v_i   = v_i + 1;
  END WHILE;

  SET p_total = v_sum;
END //

DELIMITER ;
SQL

呼び出しはこうです。

SET @total := 0;
CALL sum_to_n(10, @total);
SELECT @total;  -- 55 が返る
SQL

やっていることを言葉にすると、

カウンタ v_i を1から始める
合計 v_sum を0から始める
v_i が p_n 以下の間、
v_sum に v_i を足す
v_i を1増やす
ループが終わったら、v_sum を OUT パラメータに入れる

完全に「教科書に出てくる while 文」です。

SQLの世界でも、
こういう“普通のループ”が書ける、という感覚をここで掴んでおきます。


ループで一番大事なこと

「“必ずどこかで抜ける条件”を自分で作る」

ループを書くときに、
一番やってはいけないのが「無限ループ」です。

WHILE で言えば、

条件がずっと真のまま変わらない
カウンタを増やし忘れている

といった状態です。

さっきの sum_to_n で言えば、

SET v_i = v_i + 1; を書き忘れると、
v_i はずっと1のままなので、
v_i <= p_n が永遠に真になり、ループが終わりません。

Day12 の段階で、
ループについて一番強く意識してほしいのはここです。

ループを書くときは必ず、

初期値をどこで設定しているか
どこで条件が変化していくか
どこでループを抜けることになるか

を、自分の言葉で説明できるようにすること。

これは、SQLに限らず、
どのプログラミング言語でも通用する“ループの基本姿勢”です。


ループとSQLの“相性”を少しだけ意識する

「“本当にループが必要か?”と一度立ち止まるクセ」

ここまでループを褒めておいて、
あえて少しブレーキもかけておきます。

SQLはもともと「集合を一気に処理する」言語です。

多くのケースでは、

1行ずつループするより、
1発のUPDATE / INSERT / SELECTでまとめて処理した方が速い

という世界観です。

例えば、

1〜Nまでの合計を出すなら、本当は

SELECT p_n * (p_n + 1) / 2;
SQL

で一発ですし、
テーブルの行を1件ずつループしてUPDATEするより、
条件付きUPDATEでまとめて更新した方が効率的です。

Day12 前半では、

ループの書き方を知る
同時に「本当にループが必要か?」と一度考える癖を持つ

この2つをセットで持っておいてください。

後半では、
「どうしても1行ずつ処理したいとき」に出てくる
カーソル+ループのパターンを扱います。


Day12 前半のまとめ

ストアドプロシージャ内の条件分岐は、IF ... THEN ... ELSE ... END IF で「処理の流れ」を分ける形と、CASE WHEN ... THEN ... END で「ある値を条件に応じて別の値に変換する」形があり、前者は「退会済みなら DELETED、メール未確認なら NEED_VERIFY、それ以外は ACTIVE」といった“フロー制御”、後者は「合計金額に応じて GOLD / SILVER / BRONZE を決める」といった“値の変換”に向いている。
ループの基本としては WHILE 条件 DO ... END WHILE が最も素直で、sum_to_n のように「カウンタ v_i を初期化し、条件が成り立つ間 v_i を増やしながら合計 v_sum に足していく」という形で、「SQLの中でも普通の while 文と同じ感覚で繰り返し処理が書ける」ことを体感しておくとよい。
同時に、ループを書くときは「初期値」「条件が変化する場所」「どこでループを抜けるか」を自分の言葉で説明できることが重要で、これは無限ループを避けるための基本姿勢であり、さらにSQLの世界では「本当に1行ずつループが必要か?集合で一気に処理できないか?」と一度立ち止まる癖を持つことが、パフォーマンスと設計の両面で効いてくる。

後半では、
実際にテーブルの行を1件ずつ処理するためのカーソル+ループ、
そしてループとトランザクションをどう組み合わせるか、
という少し実務寄りのパターンに踏み込んでいきます。

タイトルとURLをコピーしました