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;
SQLIF でも書けますが、
「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件ずつ処理するためのカーソル+ループ、
そしてループとトランザクションをどう組み合わせるか、
という少し実務寄りのパターンに踏み込んでいきます。
