Laravel sort by average of property of relationship - laravel

I'm wondering if what I'd like to achieve is possible with Eloquent. Say you have a Post model with a Comments model relationship, and the Comment model has a votes property.
I know you can get a Post collection with the Comments count with:
$posts = Post::withCount('comments')->get();
And that this is how to apply a votes filter on the relationship:
$posts = Post::whereHas('comments', function (Builder $q, $vote_count) {
$q->where('votes', '>=', $vote_count);
});
But what I would like to do, ideally in one query, is filter (using where()) and sort (using orderBy()) the posts collection on an average vote score.
To get that average for one Post I would first get the sum with sum('votes') and divide it by the withCount('comments') to get the average.
But I'm wondering if I can make a filter for a collection, all in one query, that filters on the average vote score.

You can get the Post comments votes average passing an array with the relationship name as key and an anonymous function as value. Inside the function you can perform a selectRaw querying the votes column average. Then you will have available that "column" (an alias actually) to do the where and the orderBy:
$value = 5;
Post::withCount(['comments as comments_avg_votes' => function($query) {
$query->select(DB::raw('avg(votes)'));
}])
->where('comments_avg_votes', '>', $value)
->orderByDesc('comments_avg_votes')
->get();

Related

Laravel: Getting count of belongsToMany relation with where clause

I'm trying to list the amount of games players played (and finished) in a data-table. Currently I use WithCount, but it turns out to be quite slow.
$data = Player::withCount(['games' => function (Builder $query) {
$query->where('ended', 1);
}])->get();
Is there a better way to grab the count of records with a where condition that isn't as resource intensive?

Query on Eloquent based on relationship

I have 2 models, Transaction, TrackingNumber.
The relationships and fields are:
Transaction: hasMany TrackingNumber
Transaction fields: id | carrier
TrackingNumber fields: id | transaction_id
I am trying to query TrackingNumbers but I want to return only where the carrier is ups. In this sense:
a) If I use eager loading, it will return all the tracking numbers regardless of shipper and only return transaction relationship based on the where:
$collection = TrackingNumber::with(['transaction' => function($q) {
$q->where('carrier', 'ups')
}])->get();
So this is not what I want. What I want is that the $collection has only the TrackingNumbers with given carrier instead.
b) If I do something like this, the result is what I want, but it is very inefficient (as it makes 2 queries and stores data in array; also if the array is too big, it fails on whereIn.)
$transactionIds = Transaction::where('carrier', 'ups')->pluck('id');
$trackingNumbers = TrackingNumber::whereIn('id', $transactionIds)->get();
// The result is what I want but it is inefficient.
c) I also tried something like this but it doesn't care about the where :/
\DB::table('tracking_numbers')
->join('transactions', 'tracking_numbers.transaction_id', '=', 'transactions.id')
->where('transactions.carrier', 'ups')
->get();
d) I don't want to use whereHas() because it gets extremely slow with a lot of data (I have +1m rows in my tables)
What is the Laravel way to achieve this?
Using whereHas should get you the results you want:
TrackingNumber::whereHas('transaction', function ($query) {
$query->where('carrier', 'ups');
})->get();
Or, try restructuring the query from c like:
\DB::table('tracking_numbers')
->join('transactions', function ($join) {
$join->on('tracking_numbers.transaction_id', '=', 'transactions.id')
->where('transactions.carrier', 'ups');
})
->get();

How return all rows for groupBy

I use the query to get the models that the user has, but only 1 model is returned for each user. How to get all? If I set $count=3 I should receive 3 or more models in group, but only first row is returned
$items->where(/*.....*/)
->groupBy('user_id')
->havingRaw("COUNT(*) >= {$count}")->get()
UPDATE
I solved it. I created a separate function for preparing the query and used it 2 times.
I think this may be an incorrect solution, but it works
$items = Items::query();
$this->prepareQuery($request, $items)
$items->whereHas('user', function ($q) use ($count, $request){
$q->whereHas('items', function ($query) use ($request){
$this->prepareQuery($request, $query);
}, '>=', $count);
})
->paginate(4);
This may work.Sometimes take can help.
$items->where(/*.....*/)
->groupBy('user_id')
->take({{$count}});
If you have relationships set up you can quite simply call:
$model->models()
$model being the model you're wanting the list for.
models() being the name of the relationship between the two items.
For example, a post may have many comments. You can call $post->comments() to get a list of the posts comments.
You may also query this from the database directly with some eloquent magic.
Post::with('comments')->where(/*....*/)->get();
EDIT:
You can check to see if a model has X number of related models like so.
Post::has('comments', '>=', 3)->where(/*...*/)->get();

Laravel Query Builder max function and relationships

this code
$maxsub = Invoice::with(['userinvoicesubaccount'])->where([['person_id',$id]])->get();
#foreach($maxsub as $max)
{{$max->userinvoicesubaccount->subaccount_name}} </font></div>
#endforeach
Get me all subaccount's names related to my list of Invoices with a predefined ID.
What i need is to get the only subaccount name related to my list of invoices with the highest amount (that existe in my invoice table) with a predefined ID.
Any help ? thank you
First calculate the invoice with the highest amount like so:
$maxInvoice = Invoice::with('userinvoicesubaccount')->where('person_id',$id)
->orderBy('amount', 'desc')->first();
Then get the related sub accounts for that invoice like so:
$maxInvoice->userinvoicesubaccount
You can add a subquery to your with function.
$invoices = Invoice::with(['userinvoicesubaccount' => function($query) {
$query->max('amount'); // Max function gives you highest value of a given column
}])
->where([['person_id', $id]])
->get();
#foreach($invoices as $invoice)
// You will still need to call first because you will have a collection
{{$invoice->first()->userinvoicesubaccount->subaccount_name}} </font></div>
#endforeach
Take a look at https://laravel.com/docs/5.7/eloquent-relationships#eager-loading

Conditional query based on aggregation fields

Given I have this simple query:
$builder = User::select(DB::raw('users.age as age, users.restrict as restrict'))
->whereBetween("user.id",$id)
->get();
Is there any way to get users where age is lower than restrict column ?
Since both age and restrict are columns, use the whereColumn() method. Also, it looks like you want to get only records with IDs that are in $ids array. So, use whereIn():
User::whereColumn('age', '<', 'restrict')->whereIn('id', $ids)->get();
This ->where("user_id", $id) returns only one user! does not it ?
$users = DB::table('users')->whereBetween("id",$ids)
->where("age",'<','restrict')->get();
Or
$users = User::where("age",'<','restrict')->whereBetween("id",$ids)->get();

Resources