[Laravel] クエリビルダでlatest()->first()やoldest()->first()と書いてはいけない
概要
『最新の1件取得』、『最も古い1件取得』という意図で latest()->first()
や oldest()->first()
を使ってしまうとバグの原因になりかねないと感じたので、その根拠を示します。
理由
latest()
は ORDER BY created_at DESC
を発行します。
『新しいレコード順に並べる』 と抽象的に理解して使ってしまいそう(それは正しいのだけど)ですが、実際にはバルクインサートして同時に作成したデータや factory
で複数件作成したデータ、何かしらのマイグレーションスクリプトなどで生成されたデータなど、複数件のレコードが同じ created_at
であるケースも考えられます。
ですので、 latest()->first()
だけでは『最新の1件取得』を試みた際に created_at
が同じレコード中のどのレコードが返されるか制御できません。
このときどのレコードが返されるかは DB
によるし、取得する where
の条件などの SQL
によるし、実装によります。
MySQL
の場合、デフォルトで primary key
の昇順でソートされるので、 latest()->first()
とすると、 created_at
でソートした中の id
の最も小さい1件が返されます。
多くの場合、『最新の1件取得』を試みたときに複数件あるうちの id
が小さい方が返されるとは想像しづらいのではないでしょうか。これを理解せずに実装してしまうとバグの原因になりかねません。
対処法
# example1
# bad
Model::latest()->first();
# example2
# not bad
Model::latest()->orderBy('id', 'DESC')->first();
# example3
# good
Model::orderBy('created_at', 'DESC')->orderBy('id', 'DESC')->first();
example3
よりも example2
の方がコードが短くて済みますが、 example3
の方が少しコードは長くなってしまいますが、パっと見で挙動を正確に理解しやすいので個人的には好きです。
Discussion