【Laravel 8】Bulk insertはEloquent::upsertメソッドが便利
背景
Laravel 7までは複数のデータを同時にINSERTするにはDB
ファサードのinsert
メソッドで対応する必要がある。
しかしLaravel 8でEloquent::upsert
というイケてるメソッドが実装されたのでメモ。
使い方
基本
usersテーブルを考える。
複数レコードのinsertでは以下のように書ける。
$data = [
['name' => 'Ruffy', 'email' => 'ruffy@ruffy.com', 'sex' => User::SEX_MALE, 'password' => \Hash::make('password')],
['name' => 'Tonny', 'email' => 'tonny@tonny.com', 'sex' => User::SEX_MALE, 'password' => \Hash::make('password')],
['name' => 'Robin', 'email' => 'robin@robin.com', 'sex' => User::SEX_FEMALE, 'password' => \Hash::make('password')],
];
User::upsert($data, ['email']);
第2引数にはDBで設定されたプライマリーキーかユニークキーを指定する(SQL Server以外)。
All databases systems except SQL Server require the columns in the second argument provided to the upsert method to have a "primary" or "unique" index.
、、、とドキュメントには書いてあるMySQLの場合は違うようで、パラメータを渡さずともDBで設定されたユニークキーをもとにinsertが走る。[1]
insertメソッドとの比較
timestamp系のカラムを更新してくれる
DB::insert
ではcreated_at
,updated_at
に何も指定しなければnullが入るがEloquent::upsert
は指定せずとも現在時刻が入る。
// DB::insert
DB::table('users')->insert(['name' => 'Robin', 'email' => 'robin@robin.com', 'sex' => User::SEX_FEMALE, 'password' => \Hash::make('password')],);
$this->assertDatabaseHas('users', [
'name' => 'Robin',
'created_at' => null, // timestampにnullが入る
'updated_at' => null
])
// Eloquent::upsert
User::upsert(['name' => 'Ruffy', 'email' => 'ruffy@ruffy.com', 'sex' => User::SEX_MALE, 'password' => \Hash::make('password')], ['id', 'email']);
$this->assertDatabaseHas('users', [
'name' => 'Ruffy',
'created_at' => now(), //timestampに現在時刻が入る
'updated_at' => now()
]);
同一ユニークキーのレコードはupdateしてくれる
第1引数に渡したパラメータに同一IDもしくは同一ユニークキーのレコードが存在すれば、該当レコードが更新される。(updated_at
もよしなに更新される)
User::factory()->create(['name' => 'Robin', 'email' => 'robin@robin.com', 'sex' => User::SEX_MALE]);
$updatedParams = ['name' => 'Nico Robin', 'email' => 'robin@robin.com', 'sex' => User::SEX_FEMALE,'password' => \Hash::make('password')];
User::upsert($updatedParams, ['id', 'email']);
また題3引数にはupdate時に更新されるカラムを絞ることができる。
User::upsert($updatedParams, ['id', 'email'], ['name']);
ベンチマーク
1万件のデータをinsertすると処理時間は以下の様になった。
(ここでは10回実行した平均をとっている。)
DB::insert処理時間: 0.2898751826秒
Eloquent::upsert処理時間: 0.3419602284秒
Eloquent::upsert
のほうが約1.18倍時間がかかる結果に。
微妙に遅いが許容範囲内?要求されるレスポンスタイム次第では使い分ける必要があるのだろうか、、
結言
というわけで複数レコードinsert時はEloquent::upsert
を使っていきやしょう。
(サンプルコードおいてあります。)
(記事へのご指摘歓迎です。)
-
insert文を発行するメソッドでは
ON DUPLICATE KEY UPDATE
構文が使われているが、これはそもそもユニークキーやプライマリーキーを指定する必要がない。 ↩︎
Discussion