Laravel Eloquent wherehas with condition - laravel

I have a gebruikers (meaning users) table. Gebruikers are grouped in a team (many to many relationship). Gebruikers can have a breakfast, diner and supper. They eat together with their team colleagues.
Meals are registered in a mealregistration table. So there is also a many to many relationship between the gebruikers table and the mealregistration table. A gebruiker can have many mealregistrations, a mealregistration has many gebruikers.
Not every team member takes every meal. Some gebruikers only have breakfast, or dinner, or supper, or any combination of meals.
This is my code for saving a mealregistration
$userid = Auth::user()->id;
$nieuwemaaltijd = ModelsMaaltijdregistratie::create([
'user_id' => $userid,
'datum' => $this->datum,
'afdeling_id' => $this->selectedAfdeling,
'type' => $this->type,
'prijs' => appsetting($this->datum,$this->type)
]);
$nieuwemaaltijd->gebruikers()->sync($this->selectedGebruikers);
the user_id is the id of the teamleader making the registration, afdeling_id stands for the team, type is breakfast, diner or supper and price is the money charged for the meal (I made a function in a helper class for that).
I'm asked to provide a list of users that take more than one meal a day and how many days (in a certain period) they take more than one meal.
So for example if gebruiker x takes breakfast and diner on Monday, only supper on Thursday and all three meals on Wednesday, he should be in the list with count 2 (since he took more than one meal on 2 days).
I'm getting close with:
$gebruikers = Gebruiker::whereHas('maaltijdregistraties', function ($query) {
$query->groupBy('datum')->havingRaw('COUNT(*) > 1');
})->get();
It gives me a list with all the gebruikers that have at least one day on which the took more than one meal.
But how can I display the number of days that they took more than one meal?
Thanks!

You may take a look at counting related models on the Laravel documentation.
$gebruikers = Gebruiker::withCount(['maaltijdregistraties' => function (Builder $query) {
$query->groupBy('datum')->havingRaw('COUNT(*) > 1');
}])->get();
echo $gebruikers[0]->maaltijdregistraties_count;

Related

One eloquent query with whereIn clause with more than 3000 elements works but another one with the same elements and format doesn't

Hello and thank you beforehand for your help.
I've been hitting my head against a wall with this problem for a few days now so decided to ask here. I have two queries in Laravel, one grouping totals by week, and the other by month. The week one works fine but for some reason the month one doesn't, the only difference in essentially the query is that the weekly one is calculated yearly but in a different period (starting in week 48 of last year and ending in week 47 of this year), while the monthly is just the real year. The only other difference is that the week query is inside an if to show the right thata in those final weeks of the year.
$weeklySalesLastYear = Invoice::where(function ($query) use ($year, $client_ids){
$query->where('year', $year-2)->where('week', '>=', 48)->whereIn('client_id', $client_ids);
})->orWhere(function($query) use ($year, $client_ids){
$query->where('year', $year-1)->where('week', '<=', 47)->whereIn('client_id', $client_ids);
})->groupBy('week')->selectRaw('sum(total) as total, week')->get();
That is my weekly query which works perfectly.
$sortedMonthlySalesLastYear = DB::table('invoices')
->where('year', $year-1)->whereIn('client_id', $client_ids)
->groupBy('month')->selectRaw('sum(total) as total, month')->get();
And this is my monthly query which doesn't work. I know that there is an issue with whereIn clauses in eloquent where they don't accept a big number of elements for some reason, but I'm wondering why one works and not the other one and if there is a solution to it. I also want it to be an object, I've tried using a raw query but it throws an array instead, and I would rather avoid using that. This is the one that worked.
$sortedMonthlySalesLastYear = DB::select( DB::raw("SELECT SUM(total) AS total, month FROM invoices WHERE year = '$lastYear' AND client_id IN ($client_ids_query) GROUP BY month"))
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->string('month');
$table->integer('year');
$table->integer('week');
$table->integer('client_id')->index()->unsigned();
$table->integer('product_id')->index()->unsigned();
$table->integer('quantity');
$table->float('total');
$table->double('discount');
});
This is what my invoices migration looks like, the client relates to the user and that's how I get the arrays.
This is what the monthly query returns:
[2022-05-02 23:40:05] local.INFO: monthly sales:
[2022-05-02 23:40:05] local.INFO: []
And this is what the weekly one returns (it's a larger set
but this is a sample of what it throws to show its working.)
[2022-05-02 23:42:42] local.INFO: weekly sales:
[2022-05-02 23:42:42] local.INFO:
[{"total":536190.4699999997,"week":1},
{"total":568192.6700000003,"week":2},
{"total":1613808.48,"week":3},
{"total":878447.3600000001,"week":4}...]
An example of a few invoices I'm trying to process is this (there are more than 130K invoices in the database):
I'd appreciate any help and if you have a solution to this, I mostly just prefer to stay using eloquent for the code to look cleaner. Thank you.
I also have to add that the query returns the expected values if I sign in with any other user since the range of clients they have is much smaller.
I figured it out after so long. The only thing I did was implode the client_ids collection and then explode it into an array. No idea why it does accept a big array and not a big collection, and still no idea about the discrepancy between the queries.
$clients = Client::where('user_id', $user_id)->get('id');
$imp = $clients->implode('id', ', ');
$client_ids = explode(', ', $imp);
All queries work with that.

Laravel 8- Eloquent order table results in days array

I am trying to assemble a graph of tasks per user/day, but I can't find how to order it easily.
I make the following queries to collect the tasks;
$tasksLastMonth = Task::where('user_id', Auth::user()->id)
->whereMonth('date', Carbon::now()->month)
->with('client')->get();
$tasksLastWeek = Task::where('user_id', Auth::user()->id)
->where('date', [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()])
->with('client')->get();
On the other hand, I have two arrays with the days of the week and the month for the Xaxis of graph (are 2 graphs, one for week, and other for month)
$weekDays // [7,8,9,10,11,12,13]
$week // [1,2,3,4,.....,28]
Now, I need two arrays of the same length as the days of the week and month, for the Y axis, containing the number of tasks for each day. For example, if on day 8 there are 5 tasks it would look something like this:
$tasksInWeek = [0,5,0,0,0,0,0];
I also need other arrays for the number of clients for each day, but only the different ones. If one day there are 2 tasks for the same client, I only have to add 1 client that day.
I may be able to do it with just the query, but I can't find a way.
This should work:
$groupedTasks = Task::where('user_id', Auth::user()->id)
->whereMonth('date', Carbon::now()->month)
->with('client')
->get()
->groupBy(function ($item) {
return $item->date->format('Y-m-d');
})
->map(function ($items, $date) {
return $items->unique('user_id');
});
The idea here is to, first, group the elements by the date (instead of using an integer) and then, on each group just keep the distinct users.
You can duplicate almost the same logic to get the data for the weekly chart.

Laravel - Advanced Sum Aggregate in Laravel Eloquent

I have this table in mysql:
student_payment : id, student_class(varchar), student_code(varchar), gender(varchar), amount(float), created_at (timestamp)
Model name is is StudentPayment
What I want to achieve is that:
If student_class is JSS1, it checks the amount paid by each gender and place it in the respective column
If student_class is JSS2, it checks the amount paid by each gender and place it in the respective column
If student_class is SSS1, it checks the amount paid by everyone, both male and female
If student_class is SSS2, it checks the amount paid by everyone, both male and female
It sums everything vertically and horizontally
It groups everything by date (created_at)
I have this query in my Controller, but don't know how to continue
$students = DB::table('student_payment')
->select(
'student_class',
'gender',
'amount',
DB::raw('DATE(created_at) as created_date')
)
->orderByRaw('created_at DESC');
The result I want to achieve is as shown below:
How do I complete this query in the controller to achieve this result. Thanks
Following Code will give you grouping of student payment data,
$payments = DB::table('student_payment')->orderBy('created_at', 'DESC')->get();
$data = [];
foreach ($payments as $payment){
if($payment->gender=="male"){
$data[$payment->student_class]['male'][] = $payment->amount;
}elseif($payment->gender=="female"){
$data[$payment->student_class]['female'][] = $payment->amount;
}
}
Now you can calculate the sum of amounts using array_sum function. For example,
If you want to calculate payment of JSS1 class female student then,
array_sum(data['JSS1']['female']);
This will give the sum of payment. You can calculate like this for other as well.

In eloquent : How to get a model that has the count of a related model exactly n with a condition?

In eloquent : How to get related a model where count of related models with a condition is is exactly n?
Here is over simplification of the problem that I am facing:-
There are multiple courses in database.
One course has many students.
I need to get courses with exactly 20 female students.
So, I need to do both.
Check that count of number of students is 20.
And check the condition that they are female.
Now I can either use "wherehas" which does not let me count related students. In fact it only checks if there is at least one female student.
Course
::whereHas('students',function($q){
$q->where('gender','Female');
})
->get()
;
Or I can use "has" which lets me count the related students but does not let me check if they are female or not.
Course
::has('students','=','20')
->get()
;
I need something that allows me to do both checking the count of students and checking that they are all female. Something like this is needed:-
// NOT ALLOWED IN ELOQUENT
Course
::has('students','=','20',function($q){
$q->where('gender','Female');
})
->get()
;
What to do?
Per the Eloquent Relationships Documentation you can do something like this:
Course
::whereHas('students',function($q){
$q->where('gender','Female');
}, '=', 20)
->get()

Sort by average value of an one to many related table column

I have 2 models; Post and Rating
The Rating model contains an amount column which specifies how high something has been rated. This is based on 5 star rating so the amount can be a value from 1-5
The Post model has a one to many relation with the rating model and function called Ratings that returns the hasMany.
I'd like to get the 5 latest posts based on the average rating. For the average rating I've created a function that can be seen below
Note: the plural(Ratings) returns the hasMany relation where as the singular(Rating) returns a value which is the average rating
public function Rating(){
return floor($this->Ratings()->avg('rating'));
}
Is it possible to retrieve posts ordered by the avg rating using the Eloquent QueryBuilder?
Currently I'm retrieving all posts and then using the sortBy method on the collection object in order get the ones with the highest average rating. The way I'm doing this can be seen below.
$posts = Post::all();
$posts = $posts->sortByDesc(function ($post, $key) {
return $post->Rating();
});
Now if I'd only want to show 5 I still have to retrieve and sort everything which doesn't seem very resource friendly(In my eyes. I don't have any proof of this or say it is true).
So my question is the following: Is this doable using Eloquent instead of sorting the FULL collection.
Sub question: Will doing this with Eloquent instead of sorting the collection have any impact on efficiency?
You may use query builder
DB::table('post')
->select('post.id', 'AVG(rating.amount)')
->join('rating', 'post.id', '=', 'rating.post_id')
->groupBy('post.id')
->orderByRaw('AVG(rating.amount) DESC');

Resources