Laravel#7 リレーション機能で子テーブルの投稿数を数えたり得点を集計する
投稿型のサイトを作る場合は「ユーザーごとの投稿数を表示したい」「スレッドについたレスを表示したい」といった要件がありますよね。
また口コミサイトを作る場合は点数を付けるだけでなく、ユーザーの平均評価を表示したくなるのではないでしょうか。
今回は異なるデータベース同士のリレーションと、外部データベースのデータを集計して取得する方法について書きたいと思います。
目次
リレーションとは
リレーションとは簡単に言うと「異なるデータベース同士の関連付け」を指します。
例えば以下のように「User」と「Post」の2テーブルがあるとします。
ここでUserテーブルのidとPostテーブルのuser_idを用いることで、
「全てのPostはいずれかのUserに属する」という関連付けが行うことが可能です。
では、リレーションがどんな時に必要になるのでしょうか。
例えばあるテーブルに関連する情報を他のテーブルから引っ張ってきたいという要件がある場合です。
本記事の下の方でも触れますが、
「特定のユーザーの投稿だけ表示させたい」という要件もリレーションによって解決することになります。
modelの設定
あまり概念的な話をしても分かり辛いのでリレーションの設定から見ていきましょう。
リレーションの定義
リレーションの定義はModelファイルに記述します。
例えばUserとPostで1対多の主従関係を定義したい場合は、User.phpとPost.phpに以下のように追記します。
User.php
public function posts()
{
return $this->hasMany('App\Post');
}
Post.php
public function user() { return $this->belongsTo('App\User'); }
このとき1対多の関係で多数のpostsの関数名は複数形となる点に注意してください。
外部キーの規則
今回の例ではUserが親テーブル、Postが子テーブルとなるかと思います。
UserとPostは1対多の関係なので各Postは1つのUserと紐づいている必要があります。
この紐づけに行われるデータのことを外部キーと呼びます。
親テーブルと子テーブルの紐づけはデフォルトで以下のようになっております。
①子テーブルの外部キーは「親テーブル名_id」という名前のデータが用いられる。
②親テーブルの参照先は「id」という名前のデータである。
つまりPostテーブルは外部キーとしてuser_idというデータを持っている必要があり、
このuser_idという外部キーとUserテーブルのidが一致していないと紐づけは行われないということです。
外部キーの変更
とはいえ「子テーブルの外部キーを自由に設定したい」「親テーブルの参照先をid以外にしたい」という場面はあると思います。
以下のように親テーブルのモデルを開いて記述し、
第2引数に子テーブルの外部キー、第3引数に親テーブルの参照先を設定すればOKです。
User.php
public function posts()
{
return $this->hasMany('App\Post', 'user_name', 'name');
}
何らかの事情でオートインクリメントのidが使えない場合はこのやり方で乗り切りましょう。
参考サイト
Laravelで外部キーがIDではない場合のリレーション設定 | 無職からフリーランスエンジニアになること。
リレーションを活用する
withcount関数でユーザー毎の投稿数を取得する
withcount関数を使用すれば子テーブルの件数を取得することが可能です。
例えば「ユーザー一覧と一緒にその人の投稿数も表示したい」という要件を満たすことが可能です。
withCount関数には件数を取得したいテーブル名を引数として指定します。
※このとき1対多の子テーブルは複数系になるので注意
UsersviewController.php
public function userview() { $users = User::withCount('posts') ->get(); foreach ($users as $user) { $posts_count = $site->posts_count; } return view('userview', compact('users')); } }
子テーブルの情報を集計して取得する
withcount関数で件数の取得は簡単に可能です。
ですが、子テーブルの情報を集計して取得したい場合もあるでしょう。
(例えば「子テーブルにscoreというデータが存在し、平均値を取得したい」とか)
この要件はcontrollerで解決するよりもmodelの設定をした方が早いです。
例えば製品に関するレビューの平均点を取得したい場合は、以下のような記述になります。
Product.php
class Product extends Model { protected $with = ['posts']; protected $appends = ['avg_score']; public function getAvgStarAttribute() { return $this->attributes['avg_score'] = $this->posts->avg('score'); }
postを取得するときは星の平均点を常に取得し、attributeとしてデータカラムを追加します。
$withはEagerLoadしたいモデル、$appendsは追加するデータの属性名を指定します。
※EagerLoadingを行わないとscoreを取得することができません。
以下参考サイト
外部キーでavg()関数を使用するLaravel