Laravel 5.8 - How sort (orderBy) multiple relationship

Viewed 2032

I am trying to sort the serials by video views.

Relations: The Serial has a hasMany relationship to series. The Series has a hasMany relationship to episodes. The Episodes has a hasOne relationship to video. The Video has a hasMany relationship to viewcounts.

<?php
//sort method:
public function mostPopular()
    {

        $serials = Serial::with(['series.episodes.video' => function ($query) {
            $query->withCount(['videoViews' => function($query) {
            }])->orderBy('video_views_count', 'desc');
        }])->get();

        return $serials;
}

//Serial model:
public function series()
{
   return $this->hasMany(Series::class);
}


//Series model:
public function episodes()
{
   return $this->hasMany(Episode::class);
}

public function serial()
{
   return $this->belongsTo(Serial::class);
}

//Episode model:
public function video()
{
   return $this->hasOne(Video::class);
}

public function series()
{
   return $this->belongsTo(Series::class);
}

//Video model:
public function videoViews()
{
   return $this->hasMany(VideoView::class);
}

public function episode()
{
   return $this->belongsTo(Episode::class);
}


?>

I expect the sorted serials by video views (series.episodes.video.videoViews), but the actual output is not sorted.

Laravel 5.8 PHP 7

2 Answers

This is a silly one actually but I've learnt that multiple ->sortBy on collections actually are possible with no workarounds. It's just that you need to reverse the order of them. So, to sort a catalogue of artists with their album titles this would be the solution...

Instead of :

$collection->sortBy('artist')->sortBy('title');

Do this :

$collection->sortBy('title')->sortBy('artist');

Because "With" queries run as seperate queries (not subqueries as previously suggested), exposing extrapolated fuax-columns from one query to the other gets rather tricky. I'm sure there's non-documented solution in the API docs but I've never come across it. You could try putting your with and withCount in the orderBy:

Serial::orderBy(function($query) { some combo of with and withCount })

But that too will get tricky. Since either approach will hit the database multiple times, it would be just as performant to do the separation yourself and keep your sanity at the same time. This first query uses a left join, raw group by and raw select because I don't want laravel running the with query as a separate query (the problem in the first place).

$seriesWithViewCounts = VideoView::leftJoin('episodes', 'episodes.id', '=', 'video_views.episode_id')
                ->groupBy(DB::raw('episodes.series_id'))
                ->selectRaw("episodes.series_id, count(video_views.id) as views")
                ->get();
$series = Series::findMany($seriesWithViewCounts->pluck('series_id'));
foreach($series as $s) {
    $s->view_count = $seriesWithViewCounts->first(function($value, $key) use ($s) {
       return $value->series_id = $s->id
    })->video_views_count;
});
$sortedSeries = $series->sortBy('video_views_count');

This will ignore any series that has no views for all episodes, so you may want to grab those and append it to the end. Not my definition of "popular".

I'd love to see a more eloquent way of handling this, but this would do the job.

Related