Self-Curation

新卒社会人で営業マンになりました

Laravel#7 リレーション機能で子テーブルの投稿数を数えたり得点を集計する

f:id:scuration:20200802232227p:plain

投稿型のサイトを作る場合は「ユーザーごとの投稿数を表示したい」「スレッドについたレスを表示したい」といった要件がありますよね。

また口コミサイトを作る場合は点数を付けるだけでなく、ユーザーの平均評価を表示したくなるのではないでしょうか。

今回は異なるデータベース同士のリレーションと、外部データベースのデータを集計して取得する方法について書きたいと思います。




リレーションとは


リレーションとは簡単に言うと「異なるデータベース同士の関連付け」を指します。



例えば以下のように「User」と「Post」の2テーブルがあるとします。

ここでUserテーブルのidPostテーブルのuser_idを用いることで、
全てのPostはいずれかのUserに属する」という関連付けが行うことが可能です。

f:id:scuration:20200921162615p:plain


では、リレーションがどんな時に必要になるのでしょうか。
例えばあるテーブルに関連する情報を他のテーブルから引っ張ってきたいという要件がある場合です。


本記事の下の方でも触れますが、
「特定のユーザーの投稿だけ表示させたい」という要件もリレーションによって解決することになります。

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