railsで大量のデータをすでにあれば更新なければ作成する。
update + insert = upsert というらしいですね。
この操作、生のactive recordでやろうと思うとかなりの数のsql文ができて、かなり効率悪くなります。そこでactiverecord-importの出番です。こちらのgithubのwikiによるとon_duplicate_key_update
というものが対応しているみたいです。
MySQL, PostgreSQL (9.5+), and SQLite (3.24.0+) support on duplicate key update (also known as “upsert”) which allows you to specify fields whose values should be updated if a primary or unique key constraint is violated.
ふむふむ、どうやらプライマリーキー制約やユニークキー制約に引っかかったデータに対して、特定のカラムの更新をしてくれる見たいです。つまり、例えばprimary_key
がuser_id
のUser
テーブルがあったとして、
User.create(user_id: 1, name: "hogehoge", sex: 1)
User.create(user_id: 3, name: "fugafuga", sex: 0)
User.create(user_id: 5, name: "piyopiyo", sex: 0)
hashes = [
{user_id: 1, name: "Harry", sex:0},
{user_id: 2, name: "Alice", sex:1},
{user_id: 3, name: "Fred", sex:0},
{user_id: 4, name: "Bob", sex:0},
{user_id: 5, name: "Patricia", sex:1}
]
users = []
hashes.each do |item|
users << User.new(user_id: item[:user_id], name: item[:name], sex: item[:sex])
end
# MySQLバージョン
User.import users, on_duplicate_key_update: [:name]
# Postgreバージョン
User.import users, on_duplicate_key_update: {conflict_target: [:user_id], columns: [:name]}
とすると、次のように更新or新規作成される。
# 最初3つのcreateの時点
User Load (0.7ms) SELECT `users`.* FROM `users`
+---------+----------+-----+-------------------------+-------------------------+
| user_id | name | sex | created_at | updated_at |
+---------+----------+-----+-------------------------+-------------------------+
| 1 | hogehoge | 1 | 2018-10-06 15:39:51 UTC | 2018-10-06 15:39:51 UTC |
| 3 | fugafuga | 0 | 2018-10-06 15:39:51 UTC | 2018-10-06 15:39:51 UTC |
| 5 | piyopiyo | 0 | 2018-10-06 15:39:51 UTC | 2018-10-06 15:39:51 UTC |
+---------+----------+-----+-------------------------+-------------------------+
# importが終わり更新処理が終わった時点
User Load (2.6ms) SELECT `users`.* FROM `users`
+---------+----------+-----+-------------------------+-------------------------+
| user_id | name | sex | created_at | updated_at |
+---------+----------+-----+-------------------------+-------------------------+
| 1 | Harry | 1 | 2018-10-06 15:39:51 UTC | 2018-10-06 15:40:50 UTC |
| 2 | Alice | 1 | 2018-10-06 15:40:50 UTC | 2018-10-06 15:40:50 UTC |
| 3 | Fred | 0 | 2018-10-06 15:39:51 UTC | 2018-10-06 15:40:50 UTC |
| 4 | Bob | 0 | 2018-10-06 15:40:50 UTC | 2018-10-06 15:40:50 UTC |
| 5 | Patricia | 0 | 2018-10-06 15:39:51 UTC | 2018-10-06 15:40:50 UTC |
+---------+----------+-----+-------------------------+-------------------------+
ちゃんと名前だけが更新されているのがわかる。
これはかなり便利なのでは。