Home > database >  Laravel orm - Get top rows from relation
Laravel orm - Get top rows from relation

Time:11-17

I have a categories table. every category has some posts. I want to get categories with their last 10 posts. So I tried this:

Category::query()->with(['posts' => function($q) {
     $q->take(10);
}])->get();

The problem is instead of putting 10 posts in each category record, it returns a total of 10 posts in all categories items.

Expected:

C1:
   id: 1
   posts: 10 post
C2:
   id: 2
   posts: 10 post

What I got

C1:
   id: 1
   posts: 4 post
C2:
   id: 2
   posts: 6 post

CodePudding user response:

I believe this will work for you:

https://p.softonsofa.com/tweaking-eloquent-relations-how-to-get-n-related-models-per-parent/

CodePudding user response:

Would try this version


Category::with('posts')->get()->map(function($category) {
  $category->posts = $category->posts->take(10);
  return $category;
});

or optimize version try this package [link][1]


  


  [1]: https://github.com/staudenmeir/eloquent-eager-limit

CodePudding user response:

In fact, this is not a trivial task. The code you provided generates two queries:

SELECT * FROM categories;
SELECT * FROM posts WHERE category_id IN (1, 2, 3, ...) LIMIT 10;

The 2nd query selects only 10 posts, not 10 per category.

To select 10 posts per category, you can make the following query:

SELECT * FROM (
    SELECT *, row_number() OVER(PARTITION BY category_id) rn FROM posts
) posts
WHERE rn <= 10 AND category_id IN (1, 2, 3 ...);

Now let's implement it through Laravel's queryBuilder:

$result = Category::with(['posts' => static function($q) {
    $q->fromSub(
        Post::selectRaw('*, row_number() OVER(PARTITION BY category_id) rn'),
        'posts'
    )->where('rn', '<=', 10);
}])->get();

As a result, we still make only 2 queries and select only necessary posts (do not pull the entire table)

  • Related