ブログ
【Laravel】最大/最小/最新/最古のリレーションデータを取得する
Laravelでは、モデルクラスにhasOneやhasManyなどの記載をすることで、テーブルデータ同士の関連付け(リレーション)を簡単に定義できます。
今回は、1つのデータに対して複数の関連データが存在する「1対多」リレーションを対象に、値が最大のもの1件や、最新のもの1件など、ソートしたデータを簡単に取得する方法をご紹介します!(1対多のリレーションについて詳しく知りたい方は、弊社メンバーのこちらの記事がオススメです!)
検証環境は以下の通りです。
- MySQL:8.4.0
- Laravel:11.34.1
- PHP:8.3.14
最大/最小のリレーションデータを取得する
例として、店舗テーブル(shops)と商品テーブル(items)があり、店舗データ1件に対して、商品データが複数登録可能な「hasMany」と「belongsTo」の関係にあるとします。
1対多(hasMany)の関係の中から、特定のカラム値が最大/最小のものを取得するには、モデル内で hasOne()->ofMany() を使用します。
// 基本のリレーション(1対多)
// 店舗がもつ商品データを取得する
public function items()
{
return $this->hasMany(Item::class);
}
// 店舗がもつ商品データで、値段が最大のものを1件取得する
public function biggestPriceItem()
{
return $this->hasOne(Item::class)->ofMany('price', 'max');
}
// 店舗がもつ商品データで、値段が最小のものを1件取得する
public function lowestPriceItem()
{
return $this->hasOne(Item::class)->ofMany('price', 'min');
}hasOneにリレーション先モデルを指定し、ofMany の第一引数にカラム名、第二引数にmax または minを渡します。そうすると、hasMany の関係にあるリレーションデータの中から、指定したカラム値が最大/最小のものを1つに絞って取得することができます。
取得メソッドの呼び出し方は以下になります。
$shop = Shop::findOrFail($shopId);
$maxPriceItem = $shop::biggestPriceItem; // 値段が最大のものを1件取得
$minPriceItem = $shop::lowestPriceItem; // 値段が最小のものを1件取得もちろん、「hasOne」と「belongsTo」を掛け合わせずに、hasMany をベースとした基本的な Eloquent だけでも取得可能です。
ただしこの方法は、毎回 orderBy()->first() を書く必要があるので、何度も取得する可能性がある場合は、モデルにメソッドを定義した方が再利用性・可読性ともに高いかなと思います。
$shop = Shop::findOrFail($shopId);
$maxPriceItem = $shop->items()->orderBy('price', 'desc')->first(); // 最大価格1件
$minPriceItem = $shop->items()->orderBy('price', 'asc')->first(); // 最小価格1件備考:ofManyの引数について
ofMany() には配列を渡すことも可能で、複数カラムに対して 「どの値を優先するか」をまとめて指定できます。
下記の例では、2つの条件を組み合わせています。
priceの最大値を優先priceの最大値が同じ場合、idの最小値を優先
// 店舗がもつ商品データで、値段が最大のものを1件取得する
// 値段の最大値が重複した場合、idが1番小さいものを取得する
public function biggestPriceWithMinIdItem()
{
return $this->hasOne(Item::class)->ofMany([
'price' => 'max',
'id' => 'min',
]);
}最新/最古のリレーションデータを取得する
hasMany の中から最新の1件や最古の1件を取得したい場合は、 latestOfMany() と oldestOfMany() が便利です。
// 店舗がもつ商品データで、最新のものを取得する(主キー基準)
// NOTE: 引数を指定していないので、主キー(ID)が最大のものを取得
public function latestItem()
{
return $this->hasOne(Item::class)->latestOfMany();
}
// 店舗がもつ商品データで、最古のものを取得する(主キー基準)
public function oldestItem()
{
return $this->hasOne(Item::class)->oldestOfMany();
}
// 店舗がもつ商品データで、更新日が最新のものを取得する(更新日(updated_at)基準)
public function latestItemByUpdatedAt()
{
return $this->hasOne(Item::class)->latestOfMany('updated_at');
}引数なしでlatestOfMany() ・oldestOfMany() を使用すると、「主キー(ID)」を基準に値を取得します。「作成日時(created_at)」や「更新日時(updated_at)」など、ID以外のカラムを基準にしたい場合は、引数にカラム名を指定します。
取得メソッドの呼び出し方は以下になります。
$shop = Shop::findOrFail($shopId);
$latestItem = $shop::latestItem; // 最新のものを1件取得(ID基準)
$oldestItem = $shop::oldestItem; // 最古のものを1件取得(ID基準)
$latestItemByUpdatedAt = $shop::latestItemByUpdatedAt; // 最新のものを1件取得(更新日基準)(Laravel 10.x 以降) one()を使った書き方
Laravel10以降であれば、hasOne()->ofMany()の書き方だけでなく、既存のリレーションに対しone()->ofMany()を組み合わせる書き方も利用できます。hasOne にモデル指定が不要となり、定義済みのhasManyリレーションを流用できるところが便利で良いポイントです👍
参考:“Many”リレーションをHas Oneリレーションへ変換する
// 基本のリレーション(1対多)
public function items()
{
return $this->hasMany(Item::class);
}
// one()->ofMany()
public function biggestPriceItem()
{
// return $this->hasOne(Item::class)->ofMany('price', 'max');
return $this->items()->one()->ofMany('price', 'max');
}
// one()->latestOfMany()
public function latestItem()
{
// return $this->hasOne(Item::class)->latestOfMany();
return $this->items()->one()->latestOfMany();
}おわりに
今回は、Laravelでソートしたリレーションデータを簡単に取得する方法をご紹介しました。
hasOne()->ofMany() や latestOfMany()をモデルに定義するだけで、必要な場面でサッと呼び出せるのでとても便利です✨ 利用場面の多そうなソートデータがあれば、最初からモデルに定義をしておくと、コードの利便性や可読性向上に繋がるかと思います。
ご参考になれば幸いです🙇♂️
株式会社ウイングドアは福岡のシステム開発会社です。
現在、私達と一緒に"楽しく仕事が出来る仲間"として、新卒・中途採用を絶賛募集しています!
ウイングドアの仲間達となら楽しく仕事できるかも?と興味をもった方、
お気軽にお問い合わせ下さい!