週末には少しPerlを。

Perlスクリプトの学習日記です。

MySQLデータベースにINSERT文で行を挿入しようとしたときの一意キー制約によるエラーを無視する

一意キー制約

MySQLデータベースを使っていて、列が1つだけの表にどんどんレコードを追加していきたい、 ただし重複したデータは登録したくないとします。

テーブル作成時に該当の列に一意キー制約をかけておくと

mysql> create table tbl_sometest (url varchar(255), constraint unique ct_sometest(url) );
Query OK, 0 rows affected (0.11 sec)

重複したデータを登録しようとしても以下のようにエラーになって登録できません。

mysql> insert into tbl_sometest (url) values ('http://www.example.com');
Query OK, 1 row affected (0.00 sec)

mysql> select * from tbl_sometest;
+------------------------+
| url                    |
+------------------------+
| http://www.example.com |
+------------------------+
1 row in set (0.00 sec)

mysql> insert into tbl_sometest (url) values ('http://www.example.com');
ERROR 1062 (23000): Duplicate entry 'http://www.example.com' for key 'ct_sometest'

エラーコード1062は MySQLのドキュメント に記載されています。

Perl によるコード

言い方を変えればデータ重複の検査をMySQLにまかせてスクリプト側では とにもかくにもINSERT文を実行すればよい、とも言えそうです。 ただしその場合はINSERT時のデータ重複によるエラーとそれ以外の予期せぬエラーを分けて扱いたい。 データ重複によるエラーは言わば「予期されたエラー」なので単に無視したいわけです。

そこで次のようなコードを書きました。

use DBI;

# 略

my $dbh = DBI->connect($dsn, $dbUser, $dbPass);
my $query = qq{ INSERT INTO tbl_sometest(url) VALUES (?) };
my $sth = $dbh->prepare($query);
my $param = 'http://www.example.com';

my $res = $sth->execute($param);

if (not defined $res) {
    if ($sth->err == 1062 and $sth->errstr =~ /ct_sometest/) {
        ; # It is nothing
    } else {
        ERROR "string : ".$sth->errstr;
        die;
    }
}

コード中の「ct_sometest」はテーブル作成時に決めた一意キー制約の名前です。

DBI が出すエラーメッセージを抑制する

ところがこれでも標準エラー出力に 1062 のエラーメッセージが出ます。 どうやらDBIが内部で出すもののようで、これを抑制するには、 perldoc DBI によれば次のようにします。

    {
        local $sth->{PrintError} = 0; # localize and turn off for this block
        $param = 'http://www.example.com';
        $res = $sth->execute($param);
        if (not defined $res) {
            ERROR "string : ".$sth->errstr;
            die;
        }
    }