読者です 読者をやめる 読者になる 読者になる

hotoolong's blog

Railsやvimや気になったことを綴ってます

REPLACEコマンドでちょっとはまりました

MySQL

OracleのREPLACEは文字列置換に使いますが、MySQLのREPLACEはレコードが存在しないときにINSERT文に変更して処理してくれる便利なものです。
http://dev.mysql.com/doc/refman/5.1-olh/ja/replace.html
ここにリファレンスがあります。

OracleでUPDATE更新しようとして、レコードがなかったら、INSERTを自分で判断して処理しないといけないという手間がなくなります。
おぉ便利とおもってたら、ちょっとはまりました。


このREPLACEの内部ではUPDATE→なければINSERTではなくDELETE、INSERTしてます。
Railsを使う場合、テーブルのカラムにidという項目を作って、AUTO_INCREMENT設定にしてます。
Rails側でidを一意にして、レコードを扱ってます。

例えば、テーブル構成が以下のようだった場合。

mysql> CREATE TABLE test ( 
    ->   id int(11) NOT NULL AUTO_INCREMENT, 
    ->   user_id int(11) NOT NULL DEFAULT '0', 
    ->   cnt int(11) NOT NULL DEFAULT '0', 
    ->   PRIMARY KEY (id), 
    ->   UNIQUE KEY user_id (user_id) 
    -> ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 
Query OK, 0 rows affected (0.04 sec) 

レコードを作ってみます

mysql> insert into test values (NULL,1,10); 
Query OK, 1 row affected (0.02 sec) 

mysql> select * from test; 
+----+---------+-----+ 
| id | user_id | cnt | 
+----+---------+-----+ 
|  1 |       1 |  10 | 
+----+---------+-----+ 
1 row in set (0.00 sec) 

SQL文を直接つかって処理するなどして、REPLACEでidを指定しなかった場合は、AUTO_INCREMENTでidをつくるので、要注意です。

idの他にUNIQUEなKEYがあるとして、それを指定して更新なんてすると勝手にidが変わってしまいます。

idを指定して更新した場合

mysql> replace test (id,user_id,cnt) values (1,1,5); 
Query OK, 2 rows affected (0.00 sec) 

mysql> select * from test; 
+----+---------+-----+ 
| id | user_id | cnt | 
+----+---------+-----+ 
|  1 |       1 |   5 | 
+----+---------+-----+ 
1 row in set (0.00 sec) 

別のUNIQUEキーで更新した場合

mysql> replace test (user_id,cnt) values (1,8); 
Query OK, 2 rows affected (0.01 sec) 

mysql> select * from test; 
+----+---------+-----+ 
| id | user_id | cnt | 
+----+---------+-----+ 
|  2 |       1 |   8 | 
+----+---------+-----+ 
1 row in set (0.00 sec)

idを指定すればそのまま置き換えているのと同じように見えるので、UPDATEと変わらないイメージですが、別のUNIQUE KEYで更新するとidは勝手にインクリメントされてしまいます。
内部の処理がDELETE、INSERTということを理解してないとちょっと危ないですね。