简体   繁体   中英

Laravel Eloquent Model query with custom select/join/order

I have models called Post, and PostView. A Post can have many PostViews. I need to get a paginated list of Posts ordered by the count of PostViews created within the last 30 days.

I can do this using withCount :

// works but S L O W
$posts = Post::withCount(['views' => function($query) {
            $query->where('post_views.created_at', '>=', 'DATE_ADD(CURDATE(), INTERVAL -30 DAY)');
         }])
         ->orderBy('views_count')
         ->paginate(10);

However this generates a query that is really slow, taking ~24 seconds.

Using raw sql I can get the correct results much more efficiently, how can I turn that into the paginated model collection?

This generates the correct query to grab the first 10, but the resulting collection is empty. I assume it has something to do with the selectRaw

$posts = Post::selectRaw('posts.*, count(post_views.id) as views_count')
           ->join('post_views', function($join) {
                $join->on('posts.id', '=', 'post_views.post_id')
                   ->where('post_views.created_at', '>=', 'DATE_ADD(CURDATE(), INTERVAL -30 DAY)');
            })
            ->groupBy('posts.id')
            ->orderBy('views_count', 'DESC')
            ->take(10)
            ->get();

If I run the query that generates directly in mysql I do get results: (note - truncated to posts.id for brevity)

mysql> select posts.id, count(post_views.id) as views_count from `posts` inner join `post_views` on `posts`.`id` = `post_views`.`post_id` and `post_views`.`created_at` >= DATE_ADD(CURDATE(), INTERVAL -30 DAY) group by `posts`.`id` order by `views_count` desc limit 10;
+--------+-------------+
| id     | views_count |
+--------+-------------+
| 150277 |          22 |
|  43843 |           6 |
| 138789 |           4 |
| 180565 |           4 |
|  50555 |           3 |
|   2679 |           3 |
| 188572 |           3 |
| 217454 |           3 |
| 136736 |           3 |
| 126472 |           2 |
+--------+-------------+
10 rows in set (1.26 sec)

Any help is appreciated, thanks.

The query should be :

$posts = Post::selectRaw('posts.*, count(post_views.id) as views_count')
           ->join('post_views', function($join) {
                $join->on('posts.id', '=', 'post_views.post_id')
                   ->where('post_views.created_at', '>=', 'DATE_ADD(CURDATE(), INTERVAL -30 DAY)');
            })
            ->groupBy('posts.id')
            ->orderBy('views_count', 'DESC')
            // ->take(10) // no need of this
            //->get(); // will be using paginate
             ->paginate(10);

Have you tried

PostViews::where('created_at', '>=', 'DATE_ADD(CURDATE(), INTERVAL -30 DAY)')->groupBy('post_id')->selectRaw('count(*) as post_count')->with('post')->paginate(10);

Assumming you have an index on created_at

I have figured this out - the join->where clause was getting escaped and invalidating the query. Not invalid sql, just an invalid comparison such that there would always be no results.

Of course the query logger didn't show that it was escaped, so copying the query into mysql worked, making this far more difficult to track down than it should have been.

This works:

$posts = Post::select('posts.*', DB::raw('count(post_views.id) as views_count'))
->join('post_views', function($join) {
    $join->on('posts.id', '=', 'post_views.post_id')
        ->where('post_views.created_at', '>=', DB::raw('DATE_ADD(CURDATE(), INTERVAL -30 DAY)'));
    })
->groupBy('posts.id')
->orderBy('views_count', 'DESC')

With the important bit being: DB::raw('DATE_ADD(CURDATE(), INTERVAL -30 DAY)')

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM