LoginSignup
24
15

More than 1 year has passed since last update.

【Laravel8】Eloquentのupsertメソッドで複数レコードのupdateとinsertをよしなに行う

Last updated at Posted at 2022-02-16

業務で、複数のデータについて、
・既存のデータがDBにあれば更新
・既存のデータがDBになければレコード追加
を行いたいという場面がありました。

そこでupdateinsertのどちらを行うかよしなに判断してBulk insertを行ってくれるという、すごく便利なEloquentupsertメソッドの存在を知ったので、備忘録を書いておきます。

upsertメソッドとは

Laravel8から実装された機能で、1つのクエリで複数のアップサートを実行できるメソッドです。

※アップサート(upsert)とは
update + insertのことで、データの更新(update)とデータの挿入(insert)の両方の機能を併せ持つ。
対象のレコードがあればそのレコードを更新し、レコードがなければレコードの新規追加をするという場合で使う。

Laravel7までにもupdateOrCreateメソッドやfirstOrCreateメソッドという一つのレコードをアップサートするメソッドはありましたが、upsertメソッドにより複数レコードをBulk insertできるようになったようです。

使い方

usersテーブルに複数のデータをupsertする例を書いてみます。

前提として、DBのusersテーブルには
・id:1、name:taro
・id:2、name:hanako
の2つのレコードが既に存在するとします。

これに対して以下の通りupsertメソッドを実行します。

User::upsert([
  ['id' => 1, 'name' => 'taro', 'age' => 20],      // update
  ['id' => 2, 'name' => 'jiro', 'age' => 22],      // update
  ['id' => null, 'name' => 'taro', 'age' => 24]    // insert
], ['id'], ['name', 'age']);

結果は、第2引数に渡したidが一致するレコードがあった1つ目と2つ目はupdateとなり、3つ目はinsertになります。

構文

upsertメソッドは3つの引数を持ちます。

  • 第1引数
    updateまたはinsertを行いたい値を配列で渡す。

  • 第2引数
    レコードを一意に識別するカラム(プライマリーキーかユニークキー)を配列で渡す。

  • 第3引数(省略可)
    updateが行われる際に、更新する必要があるカラムを配列で渡す。
    省略すると全カラム更新される。

注意点

色んな引数を渡して動作確認をしてみて、気を付けるべきと思ったことを挙げておきます。

第2引数に指定して意味があるのはプライマリーキーもしくはユニークキーのみ

上記の例で第2引数に['id', 'name']を渡したとします。

すると2つ目のデータ['id' => 2, 'name' => 'jiro', 'age' => 22]については、既存のDBのデータはid:2、name:hanakoでありnameが一致しないのでinsertになる…と思いきや、updateが実行されました。

Readoubleにも

2番目の引数は、関連付けられたテーブル内のレコードを一意に識別するカラムをリストします。

と書いてあるので、第2引数に渡せるのは一意に識別できるカラムに限るようです。

第2引数には第1引数に渡しているカラムを渡す

先ほどの例で、以下のように第1引数にはidを渡さず、第2引数にidを渡したとします。

User::upsert([
  ['name' => 'taro', 'age' => 20],
  ['name' => 'jiro', 'age' => 22],
  ['name' => 'taro', 'age' => 24] 
], ['id'], ['name', 'age']);

この場合はidが一致するカラムがあったとしても全てinsertになりました。
なので第2引数に指定するカラムは、必ず第1引数にも渡しておかないといけません。

created_atとupdated_atは自動で設定される

モデルのタイムスタンプが有効になっている場合、created_atupdated_atのカラムは自動的に値を入れてくれます。

第1引数にはcreated_atupdated_atについて何も書かなくても、

  • updateupdated_atのみ更新
  • insertcreated_atupdated_atは処理をしたときの時刻

としてくれます。

普通の insertメソッドを使ってbulk insertする場合は、created_atupdated_atは自動入力されず明示的に含める必要があるので、これは便利だなと思いました。

【おまけ】実装例

実際のコードの書き方の例も残しておきたいと思います。

これはupsertだけでなく通常のbulk insertにも言えることですが、DBを更新するための配列をforeachfor文を回して作る際は、以下のように書くとスッキリします。

// POSTされたデータ配列 $postedAnswers をupsertする場合

$upsertAnswers = [];

foreach ($postedAnswers as $answer) {
  $upsertAnswers [] = [
    'id' => $answer['id'] ?? null,
    'body' => $answer['body'],
    'type' => $answer['type']
  ];
}

// idが一致するレコードがあれば更新、無ければレコード作成する
Answer::upsert($upsertSelections, ['id']);

私ははじめarray_pushを使って配列に要素を追加していっていましたが、先輩にレビューいただいて上記の書き方を知ったのでこれもメモでした!

参考記事

24
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
15