user860511
user860511

Reputation:

Laravel eloquent limit results

I have a working query:

Object::all()->with(['reviews' => function ($query) {
            $query->where('approved', 1);
        }])

And I want to limit the number of reviews, per object, that is returned. If I use:

Object::all()->with(['reviews' => function ($query) {
            $query->where('approved', 1)->take(1);
        }])

or

Object::all()->with(['reviews' => function ($query) {
            $query->where('approved', 1)->limit(1);
        }])

it limits the total number of reviews, where I want to limit the reviews that are returned by each object. How can I achieve this?

Upvotes: 0

Views: 8339

Answers (2)

user1669496
user1669496

Reputation: 33068

Grabbing 1 child per parent

You can create a helper relation to handle this very easily...

In your Object model

public function approvedReview()
{
    return $this->hasOne(Review::class)->where('approved', 1);
}

Then you just use that instead of your other relation.

Object::with('approvedReview')->get();


Grabbing n children per parent

If you need more than 1, things start to become quite a bit more complex. I'm adapting the code found at https://softonsofa.com/tweaking-eloquent-relations-how-to-get-n-related-models-per-parent/ for this question and using it in a trait as opposed to a BaseModel.

I created a new folder app/Traits and added a new file to this folder NPerGroup.php

namespace App\Traits;

use DB;

trait NPerGroup
{

    public function scopeNPerGroup($query, $group, $n = 10)
    {
        // queried table
        $table = ($this->getTable());

        // initialize MySQL variables inline
        $query->from( DB::raw("(SELECT @rank:=0, @group:=0) as vars, {$table}") );

        // if no columns already selected, let's select *
        if ( ! $query->getQuery()->columns)
        {
            $query->select("{$table}.*");
        }

        // make sure column aliases are unique
        $groupAlias = 'group_'.md5(time());
        $rankAlias  = 'rank_'.md5(time());

        // apply mysql variables
        $query->addSelect(DB::raw(
            "@rank := IF(@group = {$group}, @rank+1, 1) as {$rankAlias}, @group := {$group} as {$groupAlias}"
        ));

        // make sure first order clause is the group order
        $query->getQuery()->orders = (array) $query->getQuery()->orders;
        array_unshift($query->getQuery()->orders, ['column' => $group, 'direction' => 'asc']);

        // prepare subquery
        $subQuery = $query->toSql();

        // prepare new main base Query\Builder
        $newBase = $this->newQuery()
            ->from(DB::raw("({$subQuery}) as {$table}"))
            ->mergeBindings($query->getQuery())
            ->where($rankAlias, '<=', $n)
            ->getQuery();

        // replace underlying builder to get rid of previous clauses
        $query->setQuery($newBase);
    }
}

In your Object model, import the trait use App\Traits\NPerGroup; and don't forget to add use NPerGroup right under your class declaration.

Now you'd want to setup a relationship function to use the trait.

public function latestReviews()
{
    return $this->hasMany(Review::class)->latest()->nPerGroup('object_id', 3);
}

Now you can use it just like any other relationship and it will load up the 3 latest reviews for each object.

Object::with('latestReviews')->get();

Upvotes: 2

sumit
sumit

Reputation: 15464

Eloquent way

Make one relationship like below in your model class

 public function reviews() {
       return $this->hasMany( 'Reviews' );
    } 

  //you can pass parameters to make limit dynamic
  public function firstReviews() {
        return $this->reviews()->limit( 3 );
    }

Then call

Object::with('firstReviews')->get();

Faster way(if you just need one review)

Make a derived table to get the latest review 1st and then join it.

Object->select('*')
      ->leftJoin(DB::raw('(SELECT object_id, reviews  FROM reviews WHERE approved=1 ORDER BY id DESC limit 0,1
        as TMP'), function ($join) {
            $join->on ( 'TMP.object_id', '=', 'object.id' );
        })
      ->get();

Upvotes: 3

Related Questions