[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