INSERT 〜〜 ON DUPLICATE 時における、LAST_INSERT_ID()の挙動
INSERT 〜〜 ON DUPLICATE も LAST_INSERT_ID() も便利な関数なので、よく使わせてもらっています。
しかしこれらの関数を同時に利用した場合に、MySQL 5.1.12 より前のバージョンの場合に少し困る事があります。
autoincrementを利用していた場合に、LAST_INESRT_ID()の返す値が意図した値ではない場合があります。
検証
例えば下記のようなテーブル構造の場合を考えてみましょう。
CREATE TABLE `insert_test` ( `id` int(11) unsigned NOT NULL auto_increment, `test_id` int(11) NOT NULL, `test_name` varchar(10) NOT NULL default '', PRIMARY KEY (`id`), UNIQUE KEY (`test_id`) );
さて、データをinsertしてみます。
INSERT INTO insert_test (test_id,test_name) value(1,"hoge"); SELECT LAST_INSERT_ID();
この処理の結果は、下記の通り
mysql> INSERT INTO insert_test (test_id,test_name) value(1,"hoge"); Query OK, 1 row affected (0.37 sec) mysql> SELECT LAST_INSERT_ID(); +------------------+ | LAST_INSERT_ID() | +------------------+ | 1 | +------------------+ 1 row in set (0.00 sec)
これは意図した通りの動きですね。
次に、ON DUPLICATEを使ってみます。
INSERT INTO insert_test (test_id,test_name) value(2,"fuga") ON DUPLICATE KEY UPDATE test_name = 'foo'; SELECT LAST_INSERT_ID();
mysql> INSERT INTO insert_test (test_id,test_name) value(2,"fuga") ON DUPLICATE KEY UPDATE test_name = 'foo'; Query OK, 1 row affected (0.00 sec) mysql> SELECT LAST_INSERT_ID(); +------------------+ | LAST_INSERT_ID() | +------------------+ | 2 | +------------------+ 1 row in set (0.00 sec)
これも意図した通り、最後にinsertされた2というidが帰ってきました。
さて、問題は次のパターンです。
INSERT INTO insert_test (test_id,test_name) value(2,"foo") ON DUPLICATE KEY UPDATE test_name = 'foo'; SELECT LAST_INSERT_ID();
この場合、UNIQUE_KEYが設定されているtest_idの値(2)はすでに存在するため、insert処理ではなく、update処理がかかります。
mysql> INSERT INTO insert_test (test_id,test_name) value(2,"foo") ON DUPLICATE KEY UPDATE test_name = 'foo'; Query OK, 2 rows affected (0.00 sec) mysql> SELECT LAST_INSERT_ID(); +------------------+ | LAST_INSERT_ID() | +------------------+ | 3 | +------------------+ 1 row in set (0.00 sec)
お。update処理をしたはずなのに、LAST_INSERT_IDの値は、incrementされてますね。
ためしに全データを見てみても、
mysql> SELECT * FROM insert_test; +----+---------+-----------+ | id | test_id | test_name | +----+---------+-----------+ | 1 | 1 | hoge | | 2 | 2 | foo | +----+---------+-----------+ 2 rows in set (0.00 sec)
2件しかありません。
解決方法
できればinsertした場合も、updateした場合も、最後に処理したidを返してもらいたいもの。。
これを実現するには、SQLを下記のように修正します。
INSERT INTO insert_test (test_id,test_name) value(2,"foo") ON DUPLICATE KEY UPDATE test_name = 'foo', id = LAST_INSERT_ID(id); SELECT LAST_INSERT_ID();
試してみましょう。
mysql> INSERT INTO insert_test (test_id,test_name) value(2,"foo") ON DUPLICATE KEY UPDATE test_name = 'foo', id = LAST_INSERT_ID(id); Query OK, 0 rows affected (0.00 sec) mysql> SELECT LAST_INSERT_ID(); +------------------+ | LAST_INSERT_ID() | +------------------+ | 2 | +------------------+ 1 row in set (0.00 sec)
意図した動きになりました。
ま、mysqlのversionをさっさとあげろって話かもしれませんが、仕事上そう簡単にversionあげれない方もいると思うので。
ここに書いた事は、MySQL :: MySQL 5.1 リファレンスマニュアル (オンラインヘルプ) :: 8.2.5.3 INSERT ... ON DUPLICATE KEY UPDATE 構文 こちらに詳しく書いてあります。