👨‍👩‍👧‍👦

【Laravel 8】Bulk insertはEloquent::upsertメソッドが便利

2021/09/24に公開

背景

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を使っていきやしょう。
(サンプルコードおいてあります。)
(記事へのご指摘歓迎です。)

脚注
  1. insert文を発行するメソッドではON DUPLICATE KEY UPDATE構文が使われているが、これはそもそもユニークキーやプライマリーキーを指定する必要がない。 ↩︎

GitHubで編集を提案

Discussion