【问题标题】:How can I best optmize these queries in Laravel?如何最好地优化 Laravel 中的查询?
【发布时间】:2021-06-06 21:13:57
【问题描述】:

我在post.blade.php有一个简单的脚本

{{ Auth::user() ? ($post->ratings()->where('user_id',  Auth::user()->id)->exists() ? 'You rated ' . $post->userRating($post) : '') : '' }}

在我的post 模型中可以访问的两个函数是:

public function ratings() {
    return $this->hasMany(Rating::class);
}

public function userRating(Post $post) {
    return $this->ratings()->where([
        ['post_id', '=', $post->id],
        ['user_id', '=', Auth::user()->id]
    ])->first('stars')->stars / 2;
}

这是我postcontroller中的index

public function index() {
    $posts = Post::with('user')->withAvg('ratings', 'stars')->paginate(100);

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

然而,这需要大约 1 秒来加载每个页面,因为它必须执行该代码 100 次。

如果我从刀片文件中删除顶部提到的脚本,页面加载速度会更快。

我在原始 MySQL 中进行了这个查询,它加载结果的速度明显更快:

select `posts`.*, (select avg(`ratings`.`stars`) from `ratings` where `posts`.`id` = `ratings`.`post_id`) as `ratings_avg_stars`, (SELECT count(*) FROM ratings WHERE post_id = posts.id and user_id = 1) as rated from `posts` where `posts`.`deleted_at` is null

如果我把它放在我的postcontroller 我认为页面加载速度会更快,我不知道如何将 MySQL 转换为 Eloquent,我尝试了一个查询转换器,但那些卡在子查询上。

如何将 MySQL 查询转换为 Eloquent 查询?

【问题讨论】:

    标签: php laravel laravel-blade


    【解决方案1】:

    我相信您正在寻找 Constrain Your Eager Loading 并为 Post 创建访问变更器。

    你可以看到我的小提琴here

    这种方法的作用是一次性加载所有必要的数据,使用受约束的急切加载,当访问修改器将ratingAverage 附加到帖子时,它已经加载了这种关系。

    这样您可以避免任何不必要的数据库调用,或方法调用来重新计算您已有的值。

    架构:

    Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->timestamps();
    });
    
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->unsignedInteger('user_id');
        $table->string('title');
        $table->string('body');
        $table->timestamps();
    });
    
    Schema::create('ratings', function (Blueprint $table) {
        $table->increments('id');
        $table->unsignedInteger('user_id');
        $table->unsignedInteger('post_id');
        $table->integer('stars');
        $table->timestamps();
    });
    

    型号:

    use \Illuminate\Database\Eloquent\Relations\Pivot;
    
    class User extends Model
    {
        protected $guarded = [];
    }
    
    
    class Rating extends Pivot
    {
        protected $table = 'ratings';
        protected $guarded = [];
    
        public static function rate(User $user, Post $post, int $stars)
        {
            return static::create([
                'user_id' => $user->id,
                'post_id' => $post->id,
                'stars' => $stars
            ]);
        }
    
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    
        public function post()
        {
            return $this->belongsTo(Post::class);
        }
    }
    
    class Post extends Model
    {
        protected $guarded = [];
    
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    
        public function ratings()
        {
            return $this->hasMany(Rating::class);
        }
    
        public function getRatingAverageAttribute()
        {
            return $this->ratings->sum('stars') / $this->ratings->count();
        }
    }
    
    

    控制器:

    public function index() {
        $posts = Post::query()->with(['ratings' => function ($query) {
            $query->where('user_id', Auth::id());
        }])->get();
    
        return view('posts.index', [
            'posts' => $posts,
        ]);
    }
    

    最后在刀片中:

    @if($post->ratings->count())
        {{ 'You rated ' . $post->ratingAverage }}
    @endif
    

    【讨论】:

    • 我想你误会了。评分平均值是每个帖子的平均评分,我还需要获取用户是否对帖子进行了评分,如果用户对帖子进行了评分,则该用户对帖子的评分。我之前遇到过一个问题,如果有 18k 个帖子,它会加载 18k 个模型,这是不应该发生的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-03
    • 2017-01-08
    • 1970-01-01
    • 2019-06-18
    • 1970-01-01
    相关资源
    最近更新 更多