Sum of Items field in collection of collections - laravel

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();

Related

Laravel, eloquent, query: problem with retriving right data from DB

I'm trying to get the right data from the database, I'm retriving a model with a media relation via eloquent, but I want to return a photo that contains the 'main' tag stored in JSON, if this tag is missing, then I would like to return the first photo assigned to this model.
how i assign tags to media
I had 3 ideas:
Use orWhere() method, but i want more likely 'xor' than 'or'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main')->orWhere();
}]);
return $models->paginate(self::PER_PAGE);
Raw SQL, but i don't really know how to do this i tried something with JSON_EXTRACT and IF/ELSE statement, but it was to hard for me and it was a disaster
Last idea was to make 2 queries and just add media from second query if there is no tag 'main'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}]);
$models_all_media = Model:: with(['media']);
return $models->paginate(self::PER_PAGE);
but i tried something like
for($i=0; $i<count($models); $i++) {
$models->media = $models_all_media
}
but i can't do this without get() method, beacuse i don't know how to change this to LengthAwarePaginator class after using get()
try using whereHas https://laravel.com/docs/9.x/eloquent-relationships
Model::with('media')
->whereHas('media',fn($media)=>$media->whereJsonContains('custom_properties->tags', 'main'))
->paginate(self::PER_PAGE);
as per your comment you can use
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}])
->leftJoin('media', function ($join) {
$join->on('models.id', '=', 'media.model_id')
->whereNull('media.custom_properties->tags->main');
})
->groupBy('models.id')
->paginate(self::PER_PAGE);
return $models;

API filtering with pivot table

I'm using laravel 7 and i've made an API to fetch data with a VUE component.
I want to filter restaurants based on their type of food. So if i query "pizza" i want to show only restaurants that make pizza.
getTypes is a many to many relation.
public function restaurants(Request $request)
{
$ricerca = $request->input('query');
$users = Restaurant::with(array('getTypes' => function ($query) use ($ricerca) {
$query->where('name', "LIKE", "%" . $ricerca . "%");
}))->get();
return response()->json($users);
}
I tried this, but this way i don't have access to other genres, if a restaurant make pizza and meat, i don't see meat anymore.
Any advice?
create a many to many relation with restaurant and pizza.
get an array which restaurant make pize,
$array = [];
or simple var
$array = $request('query');
and put this array in query.
$data = Project::whereHas('restorant_pizza', function ($q) use ($array) {
$q->whereIn('restorant_pizza.pizza_id', $array);
})->get();
I think this idea will help you

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();

How to get count and avg of results in Laravel 5.8

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

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