17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel9 Eloquent 強力なupsertメソッド

Posted at

バルクインサートの必要性

現在私はLaravel9使用の案件に参画しています。対応しているタスクにて大量のCSVデータをDBに格納する必要がありまして、N+1問題に考慮するために一回のクエリ実行でinsertを実現できるバルクインサートの方法について調査しました。

そもそもバルクインサートとは?

バルクインサートを簡単に説明すると、

「大量のデータを1回の命令でデータベースに入れられる、insert文っぽいやつ」
引用元:「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

とのことです。LaravelやPHPのコードにはfor文やforEach文が使われることがありますが、それらを使用し同じようにinsert処理を書いてしまうとinsert処理が何千・何万回実行され、処理の低速化・メモリの圧迫に繋がるために避けたいところです。

updateOrCreateメソッドの活用

今回の実装ではCSVデータ格納時にエラーが発生し、DBに途中までしかデータが格納されないケースを考慮し、同じタイムスタンプで取り込まれたデータ行が存在した場合にはレコードをupdateする実装の方が安全性が高くinsertとupdateを兼ねているメソッドを使用したかったので、以前使用したことのあるupdateOrCreateメソッドが真っ先に浮かびました。以下が使用例です。

Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);
  • 第一引数には、データベースで検索する際の条件を連想配列で指定する。
  • 第二引数には、データが見つからなかった場合に新規挿入するデータ、またはデータが見つかった場合にupdateするデータを連想配列で指定する。

この場合ではDBのdeparturedestinationカラムに上記のそれぞれの値があった場合、priceを99・discountedを1で更新し、なかった場合は新規でdeparturedestinationpricediscountedカラムにそれぞれの値でinsertする処理になります。

ただし、多次元配列を第一引数に指定することはできないようで、forEach文を使って無理やり実装することはできますが複数のデータ行を一度に登録する肝心のバルクインサートとしては使用することができません。そこで見つけたメソッドがupsertメソッドです。

upsertメソッドの活用

Eloquentの upsert メソッドでは、対象となるレコードが存在する場合はupdateを、存在しない場合は新規insertするという挙動を実現し、第一引数には二次元の連想配列が入るため、今回の実装条件にぴったりです。

upsert の使用例を以下に示します。

Flight::upsert([
    ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
    ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);
  • 第一引数には、挿入または更新する値を連想配列で指定します。
  • 第二引数には、関連付けられたテーブル内のレコードを一意に識別するカラムを配列で指定します。(複数指定可)
  • 第三引数には、一致するレコードがDBにすでに存在する場合に更新する必要があるカラムを配列で指定します。(省略可)

この場合ではdeparturedestinationの値が、第一引数で指定したそれぞれの二次元連想配列の値と同じレコードがDBにあった場合、priceの値のみ更新するといった処理となります。第三引数を省略すると、第一引数のデータが全てupdateで反映されます。

なんと、upsertでのinsertの場合はcreated_atupdated_atのタイムスタンプも自動的に設定され、updateの場合はupdated_atのタイムスタンプのみ更新が入ります。LaravelerでありEloquenterにはたまらないメソッドとなっています。

注意点

ただし、検索するカラム(第二引数に指定するもの)はmigrationファイルで適切にprimaryキーまたはuniqueキーを設定しないと全てinsertとして処理されてしまいます。(私はデータがuniqueであればいいと勝手に思いmigrationファイルをいじらなかったため少し詰まりました。)

upsertはLaravel8から実装されたようですが、少しでもupdateされる可能性があるバルクインサート文を記述したい場合は、今後upsert文を使ってもいいかもしれません。

参考リンク

17
6
0

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
17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?