embulk-output-mysql プラグインには、MySQL へのインポート時のモード設定がある。現状だと insert, insert_direct, truncate_insert, merge, merge_direct, replace の6種類。
いま使いたい動作だと、merge もしくは merge_direct の挙動が近そうだったのだけど、ちょっとだけやりたいこととマッチしない部分があった。
例えば、こんなテーブルがあるとする(適当にいま考えた)。
CREATE TABLE ramen (
id BIGINT AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
location VARCHAR(100),
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE INDEX name_idx (name)
);
現在はこんな感じのデータが入っている状態だとする。
id | name | location | created_at | updated_at |
---|---|---|---|---|
1 | しじみラーメン和歌山 | 北海道 | 2015-07-18 | 2015-07-18 |
このテーブルへ新しいデータとして、こんなデータをブチこみたい。「しじみラーメン和歌山」の location
が間違っていた(北海道じゃなくて青森が正しい)のに気がついたので、修正が入っているんだよ。
name | location | created_at | updated_at |
---|---|---|---|
しじみラーメン和歌山 | 青森 | 2015-07-19 | 2015-07-19 |
中華のカトウ | 新潟 | 2015-07-20 | 2015-07-20 |
坂新 | 福島 | 2015-07-20 | 2015-07-20 |
これを embulk-output-mysql の merge / merge_direct モードで入れると、こんな感じになる。ちゃんと「しじみラーメン和歌山」が「青森」に変わっている。でも、本当は更新したくなかった created_at
の日付まで更新されちゃっている。この created_at
を更新対象から外せないかなぁというのが今回の悩みポイント。
id | name | location | created_at | updated_at |
---|---|---|---|---|
1 | しじみラーメン和歌山 | 青森 | 2015-07-19 | 2015-07-19 |
2 | 中華のカトウ | 新潟 | 2015-07-20 | 2015-07-20 |
3 | 坂新 | 福島 | 2015-07-20 | 2015-07-20 |
merge_direct モードで発行されるクエリは、抜粋するとこんな感じになる。実際にはプリペアドステートメントを作成した後でパラメータを設定してガシガシ回しているみたいだし、このクエリがそのまま発行されている訳ではない。
INSERT INTO ramen (
name,
location,
created_at,
updated_at)
VALUES (
'しじみラーメン和歌山',
'青森',
'2015-07-19',
'2015-07-19' )
ON DUPLICATE KEY UPDATE
name = VALUES(name),
location = VALUES(location),
created_at = VALUES(created_at),
updated_at = VALUES(updated_at)
;
このクエリを次のように書き換えられれば、この悩みは解決しそうな気がする。ON DUPLICATE KEY UPDATE
の後ろにあった created_at = VALUES(created_at)
を取り除いた感じ。
INSERT INTO ramen (
name,
location,
created_at,
updated_at)
VALUES (
'しじみラーメン和歌山',
'青森',
'2015-07-19',
'2015-07-19' )
ON DUPLICATE KEY UPDATE
name = VALUES(name),
location = VALUES(location),
updated_at = VALUES(updated_at)
;
この ON DUPLICATE KEY UPDATE
あたりの構文は embulk-output-mysql プラグインのこの辺りで組み立てられているのは把握できた。
for (int i=0; i < toTableSchema.getCount(); i++) {
if(i != 0) { sb.append(", "); }
String columnName = quoteIdentifierString(toTableSchema.getColumnName(i));
sb.append(columnName).append(" = VALUES(").append(columnName).append(")");
}
一方で、なにやら mergeKeys
という変数が渡されているいることにも気が付いた。
@Override
protected String buildPreparedMergeSql(String toTable, JdbcSchema toTableSchema, List<String> mergeKeys) throws SQLException
現在はドキュメントに書かれていないのだけど、これは、Embulk の config で次のように指定するもののようだ。
out:
type: mysql
[snip]
merge_keys:
- name
- location
このリストを使って、ON DUPLICATE KEY UPDATE
の対象カラムを指定することができるんじゃないか?と思って、手元でちょっと修正してみたら、それっぽく動くことは動いた。
ただ、embulk-output-postgresql の中での使われ方を見ると、どうもマージ対象を絞り込むためのキーとして使うものみたいだったし、用途が違っている気がする。名前が mergeKeys
ってところで気がつけよって話だけど。
mergeKeys
以外に updateColumns
みたいなリストを新たに渡すために Config 項目を増やして、それを buildPreparedMergeSql() に渡すように修正して、、、という感じで修正すれば実現可能な気がするんだけど、Java 歴が数ヶ月、Embulk 歴が3日の状態だといろいろ悩んでしまって、なかなか先に進まない。
あと、merge モードのときに UPDATE 対象のカラムが限定できる機能って一般的に欲しいものなのだろうか(オレはいま欲しいんだけど)。
この例だと created_at
が気になるだけなので、こいつにデフォルト値を設定しておいて、INSERT / UPDATE 対象から外しちゃえばいいという手法で逃げられそうな気がするけど。デフォルト値を入れられない感じのデータ場合の需要がオレの他にもあるんだろうか。。。