業務で、複数のデータについて、
・既存のデータがDBにあれば更新
・既存のデータがDBになければレコード追加
を行いたいという場面がありました。
そこでupdate
かinsert
のどちらを行うかよしなに判断してBulk insert
を行ってくれるという、すごく便利なEloquent
のupsert
メソッドの存在を知ったので、備忘録を書いておきます。
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_at
とupdated_at
のカラムは自動的に値を入れてくれます。
第1引数にはcreated_at
とupdated_at
について何も書かなくても、
-
update
:updated_at
のみ更新 -
insert
:created_at
とupdated_at
は処理をしたときの時刻
としてくれます。
普通の insert
メソッドを使ってbulk insert
する場合は、created_at
とupdated_at
は自動入力されず明示的に含める必要があるので、これは便利だなと思いました。
【おまけ】実装例
実際のコードの書き方の例も残しておきたいと思います。
これはupsert
だけでなく通常のbulk insert
にも言えることですが、DBを更新するための配列をforeach
やfor
文を回して作る際は、以下のように書くとスッキリします。
// 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
を使って配列に要素を追加していっていましたが、先輩にレビューいただいて上記の書き方を知ったのでこれもメモでした!
参考記事