BeginnerEngineerBlog
中の人
中の人

【laravel with orderBy】リレーション先のデータをwithしてorderByする

公開: 2023-06-20 00:55
更新: 2023-06-20 01:32
2829
laravel6.x with orderBy
最近withで取得したデータをorderByでソートしたい時があって調べたのでメモとして紹介します

こんにちは!

中の人です

laravelでN + 1問題を回避するために使用するwith関数ですが、メモですが、withで取得したリレーション先のデータをorderByでソートする方法を紹介します

ちなみにこの記事で利用するコードは勝手にlaravelチュートリアルシリーズで作ったTutorialアプリケーションを利用しています
laravelのバージョンは6.xですが、10でもやり方は同じです


N + 1問題とは


参考: N+1問題

簡単に言うと、関連するデータが必要な時、都度必要なデータを取得するsqlを発行するより、一発で取得した方がパフォーマンスいいよね!なことです
メインのデータをリスト化して、リレーション先のデータを表示したい時によく発生します

詳しくは参考リンクをご覧になるか、他にも自身で調べてみてください。


with関数


で、laravelのeloquentではその問題を解決するためにwith関数が用意されています

with関数とは、modelで定義したリレーション先を一発で取得してくれる関数です


使用例


📁 root/app/User.php

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Article;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    // 👇 リレーション定義
    public function articles()
    {
        return $this->hasMany(Article::class);
    }
}


articles()関数でリレーションを設定します

📁 root/app/Http/Controllers/UserController.php

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class UserController extends Controller
{
    public function index(Request $request)
    {
        $user_id = Auth::id();
        $user = User::query()
            ->where('id', '=', $user_id)
            ->with(['articles'])// 👈 これ
            ->first();

        return view(
            'user.index',
            [
                'user' => $user,
            ]
        );
    }
}


モデルで定義した関数名を文字列で設定すると、そのリレーション先のデータを一緒に取得してUserモデルを取得することができます
(withの紹介なのでAuth::user()でいいじゃんとかは無視無視)


📁 root/routes/web.php

// ~~ 省略
Route::get('/user/index', 'UserController@index')->name('user.index');

📁 root/resources/views/user/index.blade.php

@extends('layouts.app')
@section('content')
    @foreach ($user->articles as $article)
        <div>
            user_name: {{$user->name}}
        </div>
        <div style="border-bottom: solid 1px gray;">
            <div>
                title: <a href="{{route('article.show', ['id' => $article->id])}}">{{$article->title}}</a>
                <small>{{$article->created_at}}</small>
            </div>
        </div>
    @endforeach
@endsection



今user_id: 2でログインしています
で、データベースのデータは


全部user_id: 2です


idの昇順で取得されているみたいです。(約束ごとではないみたい)


withで取得するデータをソートしたいぜ



で、本題ですが、withで指定したリレーション先のデータを、idでソートしてみたいと思います

📁 root/app/Http/Controllers/UserController.php

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class UserController extends Controller
{
    public function index(Request $request)
    {
        $user_id = Auth::id();
        $user = User::query()
            ->where('id', '=', $user_id)
                    // 👇 修正
            ->with(['articles' => function ($query) {
                $query->orderByDesc('id');
            }])
            ->first();

        return view(
            'user.index',
            [
                'user' => $user,
            ]
        );
    }
}

->with(['articles' => function ($query) {

$queryには


HasManyのインスタンスが渡されています
この状態で

$query->get();

とすると、articleモデルのCollectionが返ります

ということで、modelでorderByを設定する感じで

$query->orderByDesc('id');

とすることで、任意のソートができるようになります!


この実行結果は、


ちゃんとidの降順で表示されましたね!


modelのリレーションの設定が優先される


📁 root/app/User.php

// ~~省略

    public function articles()
    {
        return $this->hasMany(Article::class)
            ->orderBy('id');// 👈 これ
    }

これで同じ処理を走らせると


元の状態になりました。


終わりに



正直chat_gptに教えてもらったんですがね。。笑

ただ、やってみたところ、全く反映されなくて、あれーchat_gpt嘘言ってるんかなーと感じたんですが、modelのリレーションにorderByが設定されていたので、試しにorderByを外したら意図する挙動になりましたので、紹介しました。(疑う心が大切や!でも、サンキューchat_gpt!)

なので、queryBuilderの中でソートしたい場合は、modelのリレーションの設定に注意してください。

もし、modelのリレーションを複数箇所で利用していて、もう全部見直すのきついっすってときは、get後のCollectionでsort()やsortBy()でソートするのがセオリーなのかなと感じます
もしくは、modelで設定されていても上書きできる方法があるかもしれません。(調べてません。(・Д・) ハア?? ので気になる方は調べて見てください!(゚∀゚ )オッケー)

今回知ったことで、個人的には、modelでorderByすると、queryBuilder処理でノイズが走る気がするので(今回の私みないにあれ?あれ?な状況)、modelでのリレーションにはorderByはない方がいいのかなぁ。。?と思いました。
この辺は実装する仕様で判断するといいでしょう!

ということで、

おつー
0
0
0
0
通信エラーが発生しました。
似たような記事