[勝手に補足]これだけは覚えておきたい!!MySQL の6つの自動変換

sakaikさんの首記の記事はうまくまとまっていますが、ところどころ勘違いする人や、じゃどう対処すればいいの?と思う人がいるのではないかなー、と思いましたので勝手に補足しておきます。(単なる便乗エントリともいいますがw)

1. [数値] 範囲外の数値は頭を押さえつけられる
2.[数値] 小数は勝手に丸められる(warningも出ない)
3.[数値] 数字以外が入っていると分かるところまでを登録してくれる
4.[文字列] 長さを超えると勝手にカットされる
5.[文字列] 「文字列連結」のつもりでうっかり + を使うな!!
6.[日付] 年を2ケタで指定する際の変換ルール

既存の環境を引き継ぐ時には、上記の気配りはエラーを減らしてくれるけれども、新規の開発には混乱を招くこともあるので、男はだまって次の設定をしましょう。

mysql> SET [GLOBAL|SESSION] sql_mode='TRADITIONAL'

いまの接続に適用したい場合は、SET SESSION〜。
今後の接続に適用したい場合は、SET GLOBAL〜で〜。

それにより、以下のものについてはエラーがでるようになります。

1. 3.4.

1.設定前
mysql> INSERT INTO numtest VALUES (2200000000);
Query OK, 1 row affected, 1 warning (0.02 sec)

1.設定後。エラーがでてデータは入らない。
mysql> SET SESSION sql_mode='TRADITIONAL';
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO numtest VALUES (2200000000);
ERROR 1264 (22003): Out of range value for column 'a' at row 1

3.設定後。
mysql> INSERT INTO numtest VALUES ('12345a');
ERROR 1265 (01000): Data truncated for column 'a' at row 1

4.設定後
mysql> CREATE TABLE strtest (a VARCHAR(10)) ENGINE=innodb;
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO strtest VALUES ("123456789ABCDE");
ERROR 1406 (22001): Data too long for column 'a' at row 1

SQLモードについては、このマニュアルをご参照ください。

では2.はどうでしょうか? これは動作を変更することはできないはずです。MySQLに限らずほとんどのデータベースでは数値同士のキャストを暗黙的に行い代入先の範囲を超えない場合には、Warningも出しません。

5.は独特なものですが、元々+を文字列連結演算子に使うのは
Sybase/Microsoft SQL Serverくらい
です。

ちなみに文字列連結はSQL標準では||ですが、MySQLでは||がORにマップされているため通常はCONCAT()関数を利用します。

||をORにマップせず、文字列連結に使うにはPIPES_AS_CONCATをsql_modeに指定します。

ちなみにOracleにもCONCAT()関数は存在しますが、引数は2個までです。
# MySQLは可変引数で設定できます。

6.については、まぁ各DB様々ですのでMySQLに限った話ではありません。素直に省略せずに書きましょう。

さて、最後に「6つの」というのは座りが悪いので、七個目をこれまた勝手に追加しておきましょう。

7.[文字列] 知らない漢字以降は勝手にカットされる
例えばMySQL 5.1/5.5のUTF8(3-byteまで)に対して、サロゲートペア(4-byte)の文字を入れようとすると、その文字だけでなく、それ以降の文字もカットされます。

mysql> CREATE TABLE t1 (c1 VARCHAR(30)) CHARSET utf8;
Query OK, 0 rows affected (0.01 sec)

mysql> -- "abc" + 4byte UTF-8(0xF0909080) + "def"
mysql> INSERT INTO t1 VALUES(0x616263F0909080646566);
Query OK, 1 row affected, 1 warning (0.02 sec)

mysql> -- "def" is truncated
mysql> SELECT C1 FROM t1;
+------+
| C1 |
+------+
| abc |
+------+
1 row in set (0.00 sec)

このように中途半端にデータが入ることを防ぐためには、冒頭で述べたようにSQL_MODEを設定します。

mysql> SET SESSION SQL_MODE='TRADITIONAL';
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO t1 VALUES(0x616263F0909080646566);
ERROR 1366 (HY000): Incorrect string value: '¥xF0¥x90¥x90¥x80de...' for column 'c1' at row 1

現状、これを防ぐためには文字列をバイナリ列として格納する(MySQL5.1/5.5)もしくは、MySQL6.0のサロゲートペア対応のキャラクタセットを利用するしかありません。詳細については、以下の松信さんの資料をご参照ください。

MySQL日本語問題洗い出し (2008.6 OSC.DB)

それでは、また!

[2010-03-04 追記]
本家のほうに、補足記事がでました。いろいろと先走って補足してすまねえっす。

[mysql]MySQLの自動変換を丁重にお断りするためのたった1種類の呪文