Some background first, I have a contact model to keep track of a user's contacts, and I have an anniversary model, they are linked as Many-to-Many with a pivot table (Each user can decide his/her own anniversaries and then add those to a contact, so the anniversaries are not hard coded).
So each User hasMany Anniversaries and every Contact can have many Anniversaries, while an Anniversary can have many Contacts.
For example, to get upcoming birthdays for all contacts for the next x months, I use the following query:
auth()->user()->contacts()->whereBetween(DB::raw('DATE_FORMAT(birthdate, "%m-%d")'), array(
Carbon::now()->format('m-d'),
Carbon::now()->addMonths($months)->format('m-d')))
->get();
This works perfectly. However, for the anniversaries it is a bit more complicated. An anniversary at this moment can have a frequency of monthly, quarterly, semi-annually and annually. This is stored as a string in the pivot table anniversary_contact.
I would like to use a similar query for this as the above one, but I am wondering if this is at all possible without having to repeat a lot of orWhereBetween like below:
In the below query I check for upcoming anniversaries with a frequency of monthly for the next 3 days:
$anniversaries = auth()->user()->contacts()->get()->each(function ($contact)
{
$contact->anniversaries->each(function ($anniversary) {
return $anniversary->whereBetween(DB::raw('DATE_FORMAT(date, "%m-%d")'), array(
Carbon::now()->format('m-d'),
Carbon::now()->addDays(3)->format('m-d')))
->orWhereBetween(DB::raw('DATE_FORMAT(date, "%m-%d")'), array(
Carbon::now()->addMonths(1)->format('m-d'),
Carbon::now()->addDays(3)->format('m-d')))
->get();
// Left the rest of the 10 "orWhereBetween" out of it in this example
});
});
Although the above repetition is a seperate issue, my main issue is this:
DB::raw('DATE_FORMAT(date, "%m-%d")')
There is no date field in the anniversaries table, it is in the pivot table.
Main question: How to use the above query to get the results I need, from checking against the pivot date?
Bonus question: Can the above query be made "simpler" or "smaller" without having to use a lot of orWhereBetween statements? If I need to check an upcoming anniversary that is a monthly one, I will have 12 orWhereBetween statements. Can this be reduced?
Or should I forget about checking it on the database level and instead get all anniversaries and use Carbon to check against upcoming anniversaries?
Related
In the application, we have a feature where it will display all the years as tab. Each tab has amount(where the amount is the summarized) but at the same time it is have data.
Scenario
Just to give a background about the application, it is a car seller
Imagine you are the customer, you started to use this application way back 2019. You clicked this module to check the summarized of each year
Let's assume we have 2500 data for each year.
In current year I want to display the total amount of all previous years
2023 tab is the active, from 2019 - 2022 you have sold 300,000 each year
In getting the previous amount, I used the DB raw sum function to get the summation.
Question: Does DB raw sum function lessen the heavy query to get the summation?
You can accomplish this with eloquent, without having to load all of the inner relationship data.
Normally programmers do the following:
Model:
public function children()
{
return $this->hasMany(Children::class);
}
In the Controller:
$models = Model::with('children')->get();
Then they load the data normally in blade and count:
#foreach($models as $model)
<p>{{ $model->children->count() }}</p>
#endforeach
Instead you can do the following:
In the Controller:
$models = Model::withCount(['children'])->get();
In the blade you load the data with the count:
#foreach($models as $model)
<p>{{ $model->children_count }}</p>
#endforeach
The withCount method will place a {relation}_count attribute on the resulting models
Use Barry DebugBar to test out the speed with heavy data, you will notice that doing it this way will reduce heavily the time needed to load the relationship, since you are not loading all of the inner relationship, rather you are just counting the inner relationship and displaying it.
For withCount Reference: https://laravel.com/docs/9.x/eloquent-relationships#aggregating-related-models
For withSum Reference: https://laravel.com/docs/9.x/eloquent-relationships#other-aggregate-functions
You can accomplish the same thing using withSum() and you can do this for deeper level of relationships as well.
Create and maintain a summary table, with year, and the sum for that year.
Working from that table, the query would be much faster, even if you had to do multiple summing.
Or, that table could have the cumulative amount since day-1. Then, the sum for "prior to 2023" is "cumulative through 2022" minus the "cumulative through 2018".
Summary Tables
I have a DB, "views," with many, many entries. I also have a "Courses" table, which these views are one-many related to. In Laravel Nova, I can get a metric of all views over time for a course with some code like this:
public function calculate(Request $request)
{
return $this->countByDays($request, view::where('viewable_id', $request->resourceId));
}
In this case, viewable_id is the id of the course, and $request->resourceId gives the ID of the course to sort by. Pretty simple.
However, now things get a little difficult. I have another model called Teachers. Each Teacher can have many courses, also in a one-many relationship. How do I get a metric of views over time for all the courses that teacher teaches?
I assumed the simplest way to do this would be to create a Laravel Collection with all courses the Teacher teaches (not exactly efficient), and then select all views in the database where viewable_id matches one of the courses in that list. Of course, by posting this, I couldn't figure out how to do that.
Of course, once this is figured out, I'd love to do the same thing for Categories (though that should function in a very identical manner to Teachers, so I don't need to ask that question).
How do I get a metric of views over time for all the courses that teacher teaches?
This should be the "countByDays" of views where the viewable_id is in the list of course ids that the teacher teaches.
An SQL query statement to achieve that is given below:
select * from "views"
where "viewable_id" in (select "id" from "courses" where "teacher_id" = ?)
The Eloquent query should be similar to:
$this->countByDays($request,
view::whereIn(
'viewable_id',
Course::with('teacher')
->select('id')
->where('teacher_id', $request->resourceId)
)
);
I have a one to many relationship between notification and alerFrequencies table. they have models for both. I wrote this function to extract all the latest created_at time stamp from the alerFrequencies table.
Example, if I have one website in my notification table with created_at time stamps in the alert table, it should return me the latest time stamp only. If I have 2 websites in the notification table with a different time stamp, it should return the time stamp for the 2 websites apart.
Here it returns only the latest regardless of the number of websites in the notification table. ony one who get a better idea to write the query, i would appreciate all kinds of help and suggestions.
public function alert(){
$alert_timestamp = AlertFrequency::with('notification')->select('created_at')->groupBy('created_at')->orderBy('created_at','DESC')->first();
$alert_timestamp=$alert_timestamp->created_at->toDateTimeString();
if($alert_timestamp==null){
return false;
}
return $alert_timestamp;
}
in the database i have to tables notifications and alerFrequencies table, with one to many relationship. the notification_id is a foreign key in the alertFreqency table. notification has columns: is, website url and alertFrequencies has : id. notification_id and created_at. now i want to get the latest created_at for every website in the notification table.
There are a few things that you are doing wrong. First you are querying AlertFrequency::with('notification') while from what I am understanding you might want something like Notification::with('alertfrequencies'). The second thing is that you are selecting just one column. Selecting just one column makes the with function useless. Third thing you are doing wrong is grouping by the created_at column. This simply does nothing for your needs. You would have to groupBy('notification_id'). I wrote the above explanations so you can understand better the logic of your application, while I am giving a possible solution below.
Given that you want the latest timestamp of a notification for each website, then a possible approach would be to add an accessor in you Notification model.
public function getLastTimestampAttribute(){
return AlertFrequency::where('notification_id', $this->attributes['id'])->order_by('created_at','desc')->first()->created_at;
}
Then you can easily access the latest timestamp for each notification by doing $notification->last_timestamp, supposing that $notification is your object on the view.
I have three tables: users, purchase_orders and approvals.
One purchase_order has to be approved by multiple users.
When a new purchase_order gets created, I also create 3 pending approvals belonging to that PO.
The approvals table has a field allowed_user_type that determines who can approve it.
I can't figure out, what is the Eloquent way of selecting the pending purchase orders that can be approved by a specific user, as these are determined from the approvals table.
So far I can pull the pending approvals from the approvals table for a user with the following in the User model.
public function approvals_pending()
{
return $this->hasMany('App\Approval', 'allowed_user_type', 'user_type')
->where('approved', '=', 0);
}
The question is, how do I combine this with a theoretical filter?
I mean ideally, I would love to write:
return $this->hasMany('App\PO')->whereIn('id', '=', $this->approvals_pending()->get()->po_id);
Or something like that...
Any ideas would be greatly appreciated.
OK, for anyone interested I found a solution:
It's very close to what I thought I would have to write.
The lists method basically creates a single array out of the selected field, so it can be plugged-in directly to a whereIn method like so:
return \App\PO::whereIn('id', $this->approvals_pending()->lists('po_id'));
I don't know if this is the most Eloquent way of doing this but it does work.
I have 3 models: Schedule, Shift, and User. A Schedule has many Shift, and each Shift belongs to a Schedule and has one User.
To get my schedule (with shifts and user), I use:
Schedule::with(['shifts', 'shifts.user'])->find($id);
I want to order this schedule by a specific user id - i.e. if you are looking at a schedule, I want your shift to be the first one on the schedule.
All I could find online was how to order the parent Eloquent model (i.e. Schedule here) using orderByRaw()
User::orderByRaw(DB::raw("FIELD(id, $user_id)"));
I tried to put this in the $query of the with statement, but this didn't work:
Schedule::with(
[
'shifts',
'shifts.user' => function($query) use ($user_id)
{
$query->orderByRaw(DB::raw("FIELD(id, $user_id)"));
}
])->find($id);
Any ideas?