スタッフブログ

  • 最新配信
  • RSS

MySQLのストアドプロシージャの使い方

 : 技術全般 2008/12/3 15:52

Blogger's Avatar

satoです。
今回は普段あまり使われないと思うMySQLのストアドプロシージャについて解説したいと思います。

MySQL5.0以降ではストアドプロシージャが使えるようになっています。
通常MySQLでストアドを使うことはないとは思いますが、一連の、CREATE TABLE等とセットでDB処理を書く時などは便利です。

ストアドプロシージャは各RDBMSで独自に実装していることが多いのですが、MySQLは標準SQL準拠で実装されており、それなりに汎用性があるものになっています。とは言っても、PL/SQLとは違うので、どこで汎用的かと言われると難しいです。
PL/SQLとは違いがありますが、大枠としては似ている部分は多いと思います。

■ストアドプロシージャの作成
ストアドプロシージャの作成にはCREATE PROCEDURE命令を使います。
DELIMITER //
CREATE PROCEDURE test_proc(in f int)
BEGIN
  INSERT INTO test_table(val) VALUES(f);
END;
//
DELIMITER ;

DELIMITER命令は、文の区切り文字(デリミタ)を変更します。
わかりづらいのですが、CREATE PROCEDURE命令は、「CREATE PROCEDURE」から「END;//」までが1つの文になり、その中で実行する命令を書くので、例えば、
CREATE PROCEDURE test_proc(in f int)
BEGIN
  INSERT INTO test_table(val) VALUES(f);
END;
;

と、DELIMITER無しにすると、「CREATE PROCEDURE」が「VALUES(f);」で終了してしまい、エラーになってしまいます。これを回避するために一時的にデリミタを//にしています。

これで定義した後に、
CALL test_proc(1);

とすると、INSERT INTOが実行されるのがわかると思います。

■ストアドプロシージャの削除
削除にはDROP PROCEDURE文を使います。DROP TABLE等と同様に、IF EXISTSを付けることができるので、CREATEの前に書いておくと便利かと思われます。

■ストアドファンクション
戻り値が無いものをプロシージャと呼ぶのに対し、戻り値があるものはファンクションと呼びます。
DELIMITER //
CREATE FUNCTION test_func(f int) RETURNS int DETERMINISTIC
BEGIN
  RETURN (f + 2);
END;
//
DELIMITER ;

基本的にはプロシージャと同様ですが、いくつか違う点があります。

・引数にinが不要
プロシージャの引数の先頭のinは、「この値は入力値です」というのを指示するためのもので、プロシージャの場合はoutという指定もできます。これはPHPで言うところのリファレンス渡しに近いものになります。
ファンクションの場合はRETURNで戻り値を指定するので不要です。

・戻り値の型の指定
CREATE FUNCTIONの引数リストの後に、「RETURNS int」というのがあります。ここで戻り値の型を指定します。(この場合はint)

・DETERMINISTIC
これを付けないで定義しようとすると、
ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
のようなエラーが発生する場合があります。
入力値が同じ場合に出力値が同じ場合はDETERMINISTIC、そうでない場合はNOT DETERMINISTICを指定します。バイナリログの記録に関わるようです。
SET GLOBAL log_bin_trust_function_creators = 1;
で回避することもできます。

ファンクションはSELECTで呼び出します。
test@localhost[testdb]> select test_func(1);
+--------------+
| test_func(1) |
+--------------+
|            3 |
+--------------+


■変数の定義
DROP FUNCTION IF EXISTS test_func2;
DELIMITER //
CREATE FUNCTION test_func2(f int) RETURNS int DETERMINISTIC
BEGIN
  DECLARE ret int;
  SET ret = f + 2;
  RETURN ret;
END;
//
DELIMITER ;

内部で使う変数の定義にはDECLARE文を使います。
DECLARE文は全てBEGINの直後に書く必要があります。ルーチンの途中に書くことはできません。
...
BEGIN
  DECLARE ret int;
  SET ret = f + 2;
  DECLARE ret2 int;
...

のようなのはエラーになります。
変数に値を代入するにはSET文を使います。

■カーソル
ストアドでよく使う処理として、「SELECTの結果を1行毎に何か処理したい」ということだと思います。PHPのforeach的なものです。
これを実現するにはカーソルというものを使います。
DROP TABLE IF EXISTS test_table;
CREATE TABLE test_table ( a int, b int, c int);
INSERT INTO test_table(a, b, c) VALUES(1, 2, 0);
INSERT INTO test_table(a, b, c) VALUES(2, 3, 0);
INSERT INTO test_table(a, b, c) VALUES(3, 4, 0);

DROP PROCEDURE IF EXISTS test_proc2;

DELIMITER //
CREATE PROCEDURE test_proc2()
BEGIN
    DECLARE eod tinyint;
    DECLARE in_a, in_b int;
    DECLARE out_c int;
    DECLARE cr1 cursor FOR SELECT a, b FROM test_table;
    DECLARE continue handler FOR not found SET eod = 1;
    SET eod = 0;

    OPEN cr1;
    FETCH cr1 INTO in_a, in_b;
    WHILE eod = 0 DO
        SET out_c = in_a * in_b;
        UPDATE test_table SET c = out_c WHERE a = in_a AND b = in_b;

        FETCH cr1 INTO in_a, in_b;
    END WHILE;
    CLOSE cr1;
END
//
DELIMITER ;

カーソルは、SELECTの戻り値のポインタ的なもので、cursor型として定義します。
「DECLARE cr1 cursor FOR」の後に、そのカーソルで呼び出したいSQLを記述します。
「DECLARE continue handler FOR not found SET eod = 1;」は、カーソル呼び出しの際に、データが存在しなくなったら変数eodに1をセットするというhandlerを定義します。これでSELECTが終了したかどうかを判定します。
カーソルのDECLAREは通常変数のDECLAREの後に定義する必要があります。
    DECLARE cr1 cursor FOR SELECT a, b FROM test_table;
    DECLARE out_c int;

のようにしてしまうと、
ERROR 1337 (42000): Variable or condition declaration after cursor or handler declaration

というエラーになります。

後はカーソルをOPENし、FETCHで値を取得するとINTOで指定した変数に代入され、それを処理します。
これを実行すると、

test@localhost[testdb]> SELECT * FROM test_table;
+------+------+------+
| a    | b    | c    |
+------+------+------+
|    1 |    2 |    0 |
|    2 |    3 |    0 |
|    3 |    4 |    0 |
+------+------+------+
3 rows in set (0.01 sec)

test@localhost[testdb]> CALL test_proc2();
Query OK, 1 row affected (0.01 sec)

test@localhost[testdb]> SELECT * FROM test_table;
+------+------+------+
| a    | b    | c    |
+------+------+------+
|    1 |    2 |    2 |
|    2 |    3 |    6 |
|    3 |    4 |   12 |
+------+------+------+
3 rows in set (0.00 sec)

cが反映されているのがわかると思います。


その他、詳しくはMySQLのリファレンスマニュアルを参照してください。

トラックバック

スタッフブログ最新
カテゴリ一覧

〒104-0061 東京都中央区銀座1丁目3番3号 G1ビル7階
お問い合わせ TEL 03-3524-8860

Copyright(c) 2012 RYUS.All Rights Reserved.