リレーション先に制約を設けた状態でCount / Laravel withCountにクエリ制約

リレーション先に制約を設けた状態でCountする方法を解説していきます。

withCountを使っていきますが、withによるリレーション先のデータに制約を設けたデータをcountしていきます。

School Modelを準備します。 Student ModelとhasManyでリレーションしていることとします。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class School extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
    ];

    public function student()
    {
        return $this->hasMany(Studnet::class, 'id', 'school_id');
    }

}

任意のControllerにて、制約を設けたwithCountをします。
リレーション先( student )の sexカラム = woman であるデータを限定して、countしてくれます。

$school_with_student_count = 
School::withCount(['student' => function ($query) {
    $query->where('sex', 'woman');
}])
->get();

これで、School modelに
student_count というカラム、countデータが追加されています。

Laravel Modelを””複数条件””で検索を色々/whereIn/orWhere/orWhereHas/function($query)

Modelを複数条件で抽出際の方法解説します。
複数条件といっても様々な複数条件が存在するかと思います。
使用用途別に解説していけたらと思います。

①:複数のカラムで、’どれか’のカラムの検索条件に合致する場合

orWhereを利用します。

sex, age, 複数カラムで検索をしています。

Human::
    where('sex', 'men')
    ->orWhere('age', '>=', 40)
    ->get();

②:特定のカラムで、複数の検索条件の’どれか’に該当

whereInを利用します。

Human::
    whereIn('age', [30,31,32,33,40,41,42,43])
    ->get();

ageカラムの中で、該当するものを抽出します。

③:複数のカラムで、複数の検索条件に該当する場合

whereInをorWhereで接続して利用します。 

$nationality = ['japan', 'china', 'korean'];

Human::
    whereIn('age', [30,31,32,33,40,41,42,43])
    ->orWhere(function($query) use($nationality) {
            $query->whereIn('nationality',$nationality);
        })
    ->get();

④:複数テーブルで、’どれか’の検索条件に該当

下記記事にて記述しています。

複数テーブルと書いていますが、厳密には1つのModelからリレーション先のデータを他のテーブルデータとして捉えています。

⑤:複数テーブルで、複数カラムの検索条件に該当

EducationalBackground
 の国が$countries含まれる OR 専攻が$magersに含まれる
OR
Job
 の専門性が$specialtiesに含まれる

Human::
    whereHas('EducationalBackground', function ($query) use($countries, $magers) {

        return $query->whereIn('country',$countries);
                    ->orWhere(function($query) use($magers) {
                        $query->whereIn('mager',$magers);
                    });
    })
    ->orWhereHas('Job', function ($query) use($specialties) {
        return $query->whereIn('specialty',$specialties);
    })
    ->get();

孫リレーション先のカラムを合計する方法/Laravel/N+1問題

孫リレーション先のカラムを合計する方法を解説してきます。

今回は詳しく解説していきませんが、この処理をすることでN+1問題を解決できる機会も多くあるかと思います。

子リレーションの場合は、withSum(‘{{子リレーション}}’, ‘{{対象カラム}}’)を利用して合計していけると思います。

同様に、withSum(‘{{子リレーション.孫リレーション}}’, ‘{{孫の対象カラム}}’)でいけるかな?と思っていたらNGでした。

■解決方法

①:親→孫リレーションを作成
②:親→孫リレーションにて、withSumで集計

という2ステップで実施します。

親子孫のモーチーフとしては、
学校毎のテスト点数の合計を集計する方法を考えていきます。
School → Student → Score とつながっていきます。

①:親→孫リレーションを作成

まずは、親から孫を繋ぐためのリレーションを作成します。

Modelを親、子、孫 2つ用意します。

子:Student Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'school_id',
    ];

    public function score()
    {        
        return $this->hasMany(Score::class, 'student_id', 'id');
    }

}

孫:Score Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Score extends Model
{
    use HasFactory;

    protected $fillable = [
        'score',
        'student_id',
    ];

    public function student()
    {    
        return $this->belongsTo(Student::class, 'id', 'student_id');
    }
}

親:School Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class School extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
    ];

    // このリレーションが今回の本題です。 親→孫へのリレーションを作成
    public function studentscore()
    {
        return $this->hasManyThrough(
            Score::class, // 孫 Model
            Student::class, // 子 Model
            'school_id', // 子テーブルの親ID
            'student_id', // 孫モデルの子ID
            'id',    // 親テーブルのローカルキー
            'id'    // 子テーブルのローカルキー
        )
    }

}

ポイントは、School Model の hasManyThroughを利用したリレーションメソッドです。
(親→子、子→孫へのリレーションもお忘れなく作成しておいてください。)

上記にて、親→孫リレーションを作成が出来ました。

②:親→孫リレーションにて、withSumで集計

②-1:孫からデータを合計

学校毎の学生の点数を合計する処理を書いていきます。
任意のControllerにて、

$school_score = School::
    withSum('student_score', 'score')
    ->get();

こちらで、学校毎に学生の点数合計が取得出来ます。

②-2:合計データはどこに?

合計データはどこに含まれるのでしょうか。

A. 親データに新しいカラムが追加されます。 カラム名は、

{{親孫のリレーション名}}_sum_{{合計したカラム名}}

$school_scoreの中身に、下記のカラムが追加さています。

studentscore_sum_score

ddでご確認を!

Laravel Eloquentでリレーション先のデータを付与した状態でデータ抽出

標題の通りです。

非常にシンプルでして、withを使います。

下記で紹介したModel / controllerを引用しながらwithの処理を追加していきます。

上記の$movieデータに追加して、Actor modelも一緒にリレーションされた状態でデータ抽出したいと思います。

movie Modelに リレーションにactors()を追加します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Movie extends Model
{
    use HasFactory;

    protected $fillable = [
        'movie_title',
    ];

    public function tags()
    {        
        return $this-->hasMany(Tag::class);
    }

    public function actors()
    {        
        return $this-->hasMany(Actor::class);
    }


}

Controllerの処理

->with('actors')を追加します。
public function search($words)
{
    
    $movie = LatestChannel::with('tags')
    ->whereHas('tags', function ($query) use ($words) {
            return $query->where('tag_name', "LIKE", "%".$words."%");
        })
    ->orWhere('movie_title', "LIKE", "%".$words."%")
    ->with('actors') // 追加
    ->get();

    return $movie;

}

これで、movieデータに +で movieに紐づく actorsのデータも一緒に抽出することが出来ます。

1つのModelからリレーション先の検索ORカラム検索にヒットしたデータを抽出 / Eloquent/with/orWhere

LaravelのEloquentを利用して、複数の条件でデータ抽出をします。下記の① OR ② の抽出です。

①:Modelのカラムから条件を指定
②:リレーション先の条件を指定

・利用するシチュエーション

例えば、検索窓を設置した場合。
「映画タイトル」OR「映画のハッシュタグ」どちらかにヒットさせる検索窓にする場合などです。

・Model

Movie Model と Tag Model それを繋ぐリレーションを記載しています。

①:Movie Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Movie extends Model
{
    use HasFactory;

    protected $fillable = [
        'movie_title',
    ];

    public function tags()
    {        
        return $this-->hasMany(Tag::class);
    }    
}

②:Movie Tag

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    use HasFactory;

    protected $fillable = [
        'tag_name',
        'movie_id',
    ];

}

Controllerの処理

実際にControllerで抽出する条件を書いていきます。

public function search($words)
{
    
    $movie = Movie::with('tags')
    ->whereHas('tags', function ($query) use ($words) {
            return $query->where('tag_name', "LIKE", "%".$words."%");
        })
    ->orWhere('movie_title', "LIKE", "%".$words."%")
    ->get();

    return $movie;

}

・リレーション先の条件を抽出する with 
・Or条件で抽出する orWhere
を併用することで、実装可能です。

検索窓に、映画のタイトルを検索しても、ハッシュタグを検索しても、どちらかにヒットする movie Modelのデータが抽出されます。

Laravelリーレション先のデータ個数を集計 / hasMany

Laravelのeloquentは強力なORMです。

例えば、hasManyでリレーション先につながってデータの個数を集計して、オブジェクトへ返してくれる方法があります。

Modelでリレーション記述
    public function drinkNumber()
    {
        return $this->hasMany(Join::class, 'drink_id', 'id');
    }
modelからリレーション先を自動集計したデータをオブジェクトへ
    $drinks = Drink::withCount('drinkNumber')->where('status', 1)->get();

これで、$drinksの中には、Drink Modelのオブジェクトに加えて、集計したデータも一緒に含まれます。

カラム名は下記のルールで、名称付けられます。

リレーション名_count

Laravelで多対多のリレーションを結合する方法【記事にタグ情報を付与】

Webアプリケーションを作成していくうえで、避けては通れないデータベースの作成・データ構造の作成です。

そのなかでも少し複雑になる多対多のリレーションの作成について解説していきます。

以前、下記にてリレーションについて解説しております。
今回は、多対多のみについて、少し深くです。

今回は、記事にタグを付与する多対多のリレーションをイメージしてみます。

記事A・記事B・記事C
タグ1・タグ2・タグ3

といった具体にお互いが、1対多の関係になる状態が多対多の関係性です。

作成するテーブルは、
・記事
・タグ
・記事とタグを紐づける中間テーブル

この3つです。

記事とタグを紐づける中間テーブル とは、何か。
記事IDとタグID
の紐づけが行われるテーブルです。

記事A_ID タグ1_ID
記事A_ID タグ2_ID
記事B_ID タグ2_ID
記事C_ID タグ3_ID

みたいに、各記事とタグのIDを紐づけます。

Modelに対してリレーションを記述していきます。
記事モデルに対して、タグとのリレーションを記述します。

1対多のリレーションの考え方でいくと
記事 → 中間テーブル → タグ でテーブル2つ跨ぐのでリレーションを2度する必要があると考えられますが、Laravelのリレーションはここを1度で処理する方法があります。

public function tagRelations()
{
return $this->belongsToMany('App\Models\Tag', 'tag_maps', 'article_id', 'tag_id' );
} 

簡単に解説します。

belongsToMany メソッドを使います。
第2引数は、中間テーブルとなるテーブル名称
第3引数は、モデルID(記事)と中間テーブルを紐づける、中間テーブルのカラム
第4引数は、リレーション先ID(タグ)と中間テーブルを紐づける、中間テーブルのカラム

これで、tagRelations()を使うことで
記事 → タグ へのリレーションが完成します。

Laravelのwherehasで複数のリレーションを繋げてデータ取得する方法

「Laravelのwherehasで複数のリレーションを繋げてデータ取得する方法」というタイトルですが、もう少し詳細に伝えると、
リレーションがネスト化しているデータをwherehasで条件指定してから取得する方法です。

リレーションがネスト化している??一体どういうことと思う方もいるかもしれません。

例えば、
user -> posts がリレーションされていて
posts -> comments がリレーションされている場合です。

今回やりたいことは、user -> post -> comment と一括でつなぎます。
userデータを「commentデータを条件」に取得することです。
例えば、commentで、「よろしく」と言っている条件に絞って、userデータを取得する。と言った感じです。

実際の取得方法では、下記です。
※posts, comments はリレーション名称です。

$commentUser = User::with('posts.comments')
    ->whereHas('posts.comments', function ($query) {
     return $query->where('message', "よろしく");
    })
    ->get();

ネストしたリレーションを取得する場合のポイントは1つです。
リレーションネストを. でつなぎます。

with('posts.comments')
whereHas('posts.comments', function ($query)

と言った感じです。
ご活用下さいませ。

リレーションを利用したデータ取得に関しては下記もご参考下さい。

Laravelのデータベースリレーションを活用して、別のModelのデータを取得する

Laravelのリレーションを活用することで、リレーション先の異なるテーブルとのデータ連携が可能になります。
非常に重要な機能ですので、是非試してみてください。

今回はリレーションについて2点お伝えしていきたいと思います。

( 1 ):Model内でのリレーションの設定方法
( 2 ):Eloquent で、リレーションを活用して別テーブルのデータを取得する方法

つまり、設定 → データ取得する 一連の流れをお伝えしたいと思います。

( 1 ):Model内でのLaravelリレーションの設定方法

こちらは下記サイトの「Eloquent:リレーション」を一度確認してみてください。

リレーションの方法は幾つかあります。
一番使う形式は、下記3つかと思います。基礎的な部分でいうとこの辺を抑えておけば問題ないと思います。

  • 1対1
  • 1対多
  • 多対多

イメージしやすい例は

1対1:
User_id と マイナンバー
※user_id もマイナンバーもどちらもユニークになる存在なので他のデータとの組み合わせは起きません。

1対多:
User_id と コメント履歴
※AさんのコメントはAさんにしか紐づきません。ただし、Aさんは複数コメントする場合はあります。

多 対 多 :
User_id と 好きな飲食店
※Aさんは、飲食店1、2,3が好き、Bさんは、飲食店2、3,5が好き。というように、紐づきが両データに紐づく可能性があります。

設定方法は、Model内にリレーションのメソッドを記述します。
詳細は 「Eloquent:リレーション」 こちらに書いてある通りです。
1つ注意して確認してみて欲しいのは、
データリレーションには、主従の関係性があるということです。

( 2 ):Eloquent で、リレーションを活用して別テーブルのデータを取得する方法

リレーションを設定する目的の1つは、データを取得しやすくすることです。
簡単な例で紹介します。


A // リレーション設定
※ChatRoomUser クラスから、UserクラスへID同士を紐づけしております。

 <?php

namespace App\Models\Chat;

use Illuminate\Database\Eloquent\Model;

class ChatRoomUser extends Model
{

    protected $fillable = ['chat_room_id', 'user_id', 'usertype'];    

    public function InterviewUser()
    {
        return $this->belongsTo('App\Models\User' , 'user_id' ,'id');
    }
} 

B // リレーションデータの取得
これを設定したことによって、Eloquent でChatRoomUser クラスを活用して、Userクラスの情報を取得できます。

$messageUser = ChatRoomUser::whereIn('chat_room_id', $chat_room_id)
->where('usertype', 'Interviewer')
->get();

foreach($messageUser as $user){
$email = $user->InterviewUser->email;
}
 
 

リレーションを活用した取得方法は下記です。
Eloquantで取得データ- > リレーション設定のメソッド -> リレーションした先のModelのデータカラム

今回でいうと:
・ Eloquantで取得データ  $messageUser
・ リレーション設定のメソッド  InterviewUser
・ リレーションした先のModelのデータカラム  email※Userクラスのデータカラム

リレーションデータ取得時の注意

1件のデータに対してメソッドを適用することが出来る

$messageUser は、配列の中に複数のデータが含まれている場合があります。
配列に対して、 リレーション設定のメソッド をアクセスすることは出来ません。
データを一つづつに分解して、そのデータに対してリレーションメソッドを適用しましょう。