How to get count and avg of results in Laravel 5.8 - laravel

I have the below query returning all available fiction books from all libraries:
$results = Library::with('category.books')
->whereHas('category.books', function($query) {
$query->where('available', 1);
})
->whereHas('category', function($query) {
$query->where('name', 'fiction');
})
->get();
Now, what is the best way to get the total number of books and the average rating per book (book has field rating), per library?
I assume I have to create a collection of these results somehow, apply a custom function.

You can get all the libraries while made changes to them using map() function.
You can count number of items in the collection using count() function.
You can get average by a property of items in the collection using average() function.
$libraries = $results->map(function($library) {
// All the books of the library
$books = $library->category->flatMap->books;
// set book count and average rating to the library object
$library->book_count = $books->count();
$library->average_rating = $books->average('rating');
return $library;
});
Now every library object in this $libraries collection has those new two properties called book_count and average_rating.

This could be achieved in a number of ways. From the results that you have:
$libraries = Library::with('category.books')
->whereHas('category.books', function($query) {
$query->where('available', 1);
})
->whereHas('category', function($query) {
$query->where('name', 'fiction');
})
->get();
$books = $libraries->map(function ($library) {
return $library->category->books;
})
->collapse()
->filter(); // This is an optional step to remove NULL books, if there are any.
$count = $books->count();
$avg = $books->avg('rating'); // Or $books->average('rating');
But I think a better approach could be calculating the count and average from a query starting from Book, let's define a relationship named category in the model class if you haven't have one:
class Book
{
// This could be a different type of relationships.
public function category()
{
return $this->belongsTo('category');
}
}
Then write a query.
$query = Book::where('available', 1)
->whereHas('category', function($query) {
$query->where('name', 'fiction');
});
$count = $query->count();
$avg = $query->avg(); // Or $query->average();

You just need to define the relation in library model for count average of book and rating like below. I am expecting that you have a column library_id in books table.
In your library model.
public function TotalBooks()
{
return $this->hasMany('App\Books', 'library_id')
->selectRaw('SUM(id) as total_book');
}
public function AvgRating()
{
return $this->hasMany('App\Books', 'library_id')
->selectRaw('AVG(rating) as avg_arting');
}
$data = Library::with('TotalBooks')->with('AvgRating'); // Get record

Related

Laravel, sort result on field from relation table?

I have a list with gamers and another table with game stats.
My list code is:
$gamers = Gamer::with(['lastGameStat' => function($query) {
$query->orderBy('total_points', 'DESC');
}])->paginate(20);
relation:
public function lastGameStat() {
return $this->hasOne(GameStat::class, 'gamer_id', 'id')->orderBy('created_at', 'DESC');
}
in relation table I have field: total_points and with this code I thought it's possible to sort list of gamers by total_points $query->orderBy('total_points', 'DESC');
It doesn't work, can somebody give me an advice here how can I sort the result on a field from relation table?
I guess you'll need either another relation or custom scopes to fetch various game stats of a gamer.
Second relation
Gamer.php (your model)
class Gamer
{
public function bestGameStat()
{
return $this
->hasOne(GameStat::class)
->orderBy('total_points', 'DESC');
}
}
Custom scopes
Gamer.php
class Gamer
{
public function gameStat()
{
return $this->hasOne(GameStat::class);
}
}
GameStat.php
use Illuminate\Database\Eloquent\Builder;
class GameStat
{
public function scopeBest(Builder $query)
{
return $query->orderBy('total_points', 'DESC');
}
}
In your controller:
$gamersWithTheirLatestGameStatistic = Gamer::with(['gameStat' => function($query) {
$query->latest();
}])->paginate(20);
$gamersWithTheirBestGameStatistic = Gamer::with(['gameStat' => function($query) {
$query->best();
}])->paginate(20);
Be aware as this is untested code and might not work.

How to order by column in nested level relationship in Laravel and get first order by?

I have two model.finance has many price.i want to get just one price (last record according to time) for every finance.so used function and order by and first of each orderby.but this just works for first finance and the other i get null in the with relation.
public function prices()
{
return $this->hasMany(Price::class, 'finance_id');
}
public function finances()
{
return $this->belongsTo(Finance::class, 'finance_id');
}
$finances = Finance::with(['prices' => function ($query) {
$query->orderBy('created_at', 'desc')->first();
}])->get();
Create new relationship in your Finance model to get latest price:
public function latestPrice()
{
return $this->hasOne(Price::class)->latest();
}
Change your query as below:
$finances = Finance::with('latestPrice')->get();

Sum of Items field in collection of collections

I'm trying to see if it's possible, but I would like to get the sum of a field in an item in a collection of collections.
I have the following in my controller:
$prefiltered_contacts = Contact::with(['donations' => function ($query) use ($request) {
$query->whereMonth('date_received', $request->month)
->whereYear('date_received', $request->year);
}])->get();
$contacts = $prefiltered_contacts ->filter(function ($contact) {
return $contact->donations->isNotEmpty();
});
My donation class has the following:
public function monetary_donations(){
return $this->hasMany('App\Payments_Distribution', 'donation_id','id');
}
Now the last part of this is that in the Payments_Distribution class, there is a field titled amount.
If I was coming directly from the Donation model, I would access the sum of the monetary donations as $donation->monetary_donations->sum('amount'); and I would receive the sum. But how would I go about doing this from the Contact model? Or is that even possible given that it would need to go through a collection of donations to get to the collection of monetary_donations? I'm trying to get a report of all Contacts donations (and monetary donations) and output a subtotal of the monetary_donations for that specific period.
Sum accepts a closure as the argument. So you could do something like this:
$sum = $donations->sum(function ($donation) {
return $donation->monetary_donations->sum('amount');
});
Or 1 level higher (from $contacts):
$sum = $contacts->sum(function ($contact) {
return $contact->donations->sum(function ($donation) {
return $donation->monetary_donations->sum('amount');
});
});
Edit:
I would also recommend eager loading your relationships and filtering out contacts without donations with SQL rather than collections:
$contacts = Contact::with(['donations' => function ($query) use ($request) {
$query
->with('monetary_donations')
->whereMonth('date_received', $request->month)
->whereYear('date_received', $request->year);
}])
->whereHas('donations') // Filter out those without donations with SQL
->get();

Get all championships that are teams in Eloquent

I have a tournament, a tournament can have many >
public function championships()
{
return $this->hasMany(Championship::class);
}
and a Championship hasOne Category. In Category, I have the isTeam attribute.
Now I need a function that get me all the championships that have the isTeam = 1 in Category table.
public function teamChampionships()
{
}
Of course, I have defined : $tournament->championships, $championship->category
In my controller, I get all of them:
$tournament = Tournament::with('championship.category')->find($tournament->id);
Any idea???
Try
$tournament = Tournament::with(['championships' => function ($query) {
$query->whereHas('category', function($subquery) {
$subquery->where('isTeam', '=', 1);
});
}])->get();
If the above doesn't work, try a different approach. Define isTeam() scope in Category model
public function scopeIsTeam($query) {
return $query->where('isTeam', 1);
}
Then you can use it like this
$tournament = Tournament::with('championships.categoryIsTeam')
->find($tournament->id);
Even better, create another scope in Championship that loads only teams
public function categoryTeam() {
return $this->hasOne(Category::class)->isTeam();
}
Sorry for too much information. One of those should do the job.

Laravel include relationship result if one exists

I am trying to write a query where all items are returned (products) and if a relationship exists for that particular item (many to many) then that information is included too. When I include the relationship at the moment on the query it only returns items that have that relationship rather thatn every single item, regardless of whether that relationship exists or not.
Here is my query at the moment:
public static function filterProduct($vars) {
$query = Product::query();
if((array_key_exists('order_by', $vars)) && (array_key_exists('order', $vars))) {
$query = $query->orderBy($vars['order_by'], $vars['order']);
}
if(array_key_exists('category_id', $vars) && $vars['category_id'] != 0) {
$query = $query->whereHas('categories', function($q) use ($vars) {
return $q->where('id', $vars['category_id']);
});
}
if(array_key_exists('manufacturer_id', $vars)) {
$query = $query->whereHas('manufacturer', function($q) use ($vars) {
return $q->where('id', $vars['manufacturer_id']);
});
}
$query = $query->whereHas('options', function($q) use ($vars) {
});
As you can see, when an item has the 'options' relationship I need to have that particular row include details of that relationship in the returned date. With the code as it is though it is only returning items that have this relationship rather than every single item.
Can someone advise me as to how this is achieved please?
Thanks!
I feel a bit stupid as it was so simple but it was solved by adding this:
$query = $query->with('options');

Resources