This question is really hard to word, so I apologise for the title!
Lets say I have a Rooms model and a Bookings model, 1 room can have many bookings.
I am building a table with filters, and I need to be able to run a filter on the booking relationship IF there is a booking, else grab the record anyway.
Lets say my room has a booking relationship like so
public function latestBooking(){
return $this->hasOne('App\Models\Bookings', 'room_id', 'id')
->orderBy('expiry_date', 'DESC')->limit(1);
}
The above relationship gets me the latest booking.
I'm running a 'vacant from' filter like so
if ($request->has('filters.vacant_from')) {
$rooms = $rooms->whereHas('latestBooking', function ($query) use ($request) {
$query->where('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
});
}
The issue is that this only gets rooms that have a booking and are available after the date specified. I need it to be able to search all rooms, if it does have a booking, check if the expiry date is after the date specified, if it doesn't have a latest booking, get it anyway as its a vacant room.
Basically how would I structure my filter function so that it runs like this
Search all rooms, if it has a latest booking, check its expiry date is after the date specified and only grab it if its true/vacant, if it doesn't have a latest booking then grab it anyway as it is vacant
I have a suggestion for you regarding your latestBookingRelationship. You should create another relationship and add a greater than today condition to it and maybe name it activeLatestBooking.
Then, your latest Booking should comprise all booking rooms irrespective of whether their booking is active or not.
public function activeLatestBooking(){
return $this->hasOne('App\Models\Bookings', 'room_id', 'id')
->where('expiry_date', '>', date('d/m/Y', strtotime('today')))->orderBy('expiry_date', 'DESC')->limit(1);
}
public function latestBooking(){
return $this->hasOne('App\Models\Bookings', 'room_id', 'id')
->orderBy('expiry_date', 'DESC')->limit(1);
}
Then, to answer your question:
if ($request->has('filters.vacant_from')) {
$rooms = $rooms->whereHas('latestBooking', function ($query) use ($request) {
$query->where('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
})->orWhereDoesntHave('latestBooking');
}
What the filter query does is this: It gets all latest bookings whose expiry_date is less than the vacant_from date...and the orWhereDoesntHave gets all rooms that have never been booked.
the booking relationship should be like this,
public function latestBooking(){
return $this->hasMany('App\Models\Bookings')->orderBy('expiry_date', 'DESC');
}
as this is a one to many relationship. laravel relationship
and you can write your filter like this,
if ($request->has('filters.vacant_from')) {
$rooms = $rooms->whereHas('latestBooking', function ($query) use ($request) {
$query->whereDate('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
});
}
you can use whereDate to run date queries in laravel. Hope this helps
Assuming you have hasMany relation between Rooms and Bookings model with name bookings
$rooms = Rooms::whereHas('bookings', function ($query) use ($request) {
$query->whereDate('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
})->orWhereDoesntHave('bookings')->get();
Related
I've got two tables related to each other:
Users:
id | name | email | phone
Posts:
id | user_id | body | start:date | end:date
They're related like: User can have many Posts:
public function posts()
{
return $this->hasMany(Post::class)->with('comments');
}
and in Posts model:
public function users()
{
return $this->belongsTo(User::class, 'user_id');
}
I need to get All Users with Posts where start between [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()] and same condition for end column:
$users = User::whereHas('posts', function($q) use($currentMonth){
return $q->whereBetween('start', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()])->orWhereBetween('end', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()]);
})->get();
The main problem which I got is that I didn't get any User if condition is not OK. I need All Users, even if he doesn't have any posts, and the condition isn't valid, but the array of their Posts(or object array related) should be null.
So: I get all users with posts, if he/she matches the condition of date, get's only posts which match this, if he/she doesn't have posts or it didn't match condition in query his/her relation should be null or empty, but user should be added to collection.
Is it possible to achieve?
EDIT:
I got another problem. I've got form with the specific month page view(up to 12 future months). After selecting specific one I'm submitting form and passing it to Controller which look like that:
public function monthPage(Request $request)
{
$start = Carbon::parse($request['month'])->firstOfMonth();
$end = Carbon::parse($request['month'])->lastOfMonth();
$users = User::with(['posts' => function($q) use($start, $end){
return $q->whereBetween('start', [$start->firstOfMonth(), $end->lastOfMonth()])
->orWhereBetween('end', [$start->firstOfMonth(), $end->lastOfMonth()]);
}])->get();
return view('posts.specific-month')
->with('users',$users);
}
If Users has post with start on 20th of March and end on 20th May, the query isn't returning Posts for selection(in dropdown) of April.
I got for each month the page, so it works correct if I select March - it will display it until end of month and If I select May it will be displayed until 20th. If I select April, there's nothing to return, but this date is between 20th of March and 20th of May.
Here you go:
$users = User::with(['posts' => function($q){
return $q->whereBetween('start', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()])
->orWhereBetween('end', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()]);
}])
->get();
You should create a scope in the Post model:
public function applyDateRange(Carbon $start, Carbon $end)
{
return $this->whereBetween('start', [$start, $end])
->orWhereBetween('end', [$start, $end]);
}
Then call the scope in the relation like this:
$users = User::with(['posts' => function ($q) {
$q->applyDateRange(Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth());
}])->get();
Good day. I have this relationship problem in laravel
I have three models
User
State
School
I have established the following relationships between them
a. School - User (A user can enroll in many schools)
In User Model
public function schools(){
return $this->belongsToMany('App\Models\School', 'school_user', 'user_id', 'school_id');
}
b. A school can have many users
In School Model
public function users(){
return $this->belongsToMany('App\Models\User', 'school_user', 'school_id', 'user_id');
}
c. A school belongs to a state (i.e c an be found in a state)
In School Model
public function states(){
return $this->belongsTo('App\Models\State');
}
d. A state has many schools
In State Model
public function schools(){
return $this->hasMany('App\Models\School');
}
Now, I know how to query models with immediate relationship. For example, I can get the users associated with a school and vice-versa. I can also get the schools in a state.
My Question
How do I get all users associated with schools in a given state (using a query)? Assuming all we have is just the name of a state.
And probably get all schools the users are associated with and the date(time) in which there were associated with the schools?
This is what I have done. The second query is not giving me an answer
public function details(){
//Get all schools associated with a state
$schools = State::with('schools')->where('id',1)->first();
foreach($schools->schools as $data){
//Get all users associated with the schools
$users = School::with('users')->where('id',$data->id);
dd($users);
}
}
Apart from the fact that this approach is not probably right, I am not getting any answer. Is there a query or method that can solve this?
Thanks.
You should see the related models in the relations array attribute
$school= App\Models\School::with('User', 'State')->get();
Using a whereHas you can get all users that belong to schools with the given state_id.
$state = State::find(1);
$users = User::whereHas('schools', function ($query) use ($state) {
$query->where('state_id', $state->id);
})->get();
Querying Relationship Existence
If you need even more power, you may use the whereHas and orWhereHas
methods to define additional query constraints on your has queries,
such as inspecting the content of a comment:
use Illuminate\Database\Eloquent\Builder;
// Retrieve posts with at least one comment containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
// Retrieve posts with at least ten comments containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();
Currently I have 3 tables
users
id, name
discount
id, name, active
discount_user
discount_id, user_id, start, end
I need to find all the active discounts for a current user which are currently running based on the start and the
end dates held on the pivot table.
I was hoping to be able to build a scopeActive method on the discount table to narrow down active discounts for the
current user, but I only want the date range to be added if I'm coming from the user table to find the discounts:
$discounts = User::find(1)->discounts()->active()->get(); // within date range
$active = Discount::active()->get(); // without date range check
On the user table I've extended the relations to have an 'activeDiscounts' relation which works using:
public function activeDiscounts() {
return $this->discounts()
->where('active', true)
->wherePivot('start', '>=', Carbon::now())
->wherePivot('end', '<=', Carbon::now());
}
Though this works, I don't think it's really best practice and would prefer to be able to use a scope on the discount.
Is there any way I can tell if I'm coming from the user table or through a pivot on the scope query? Also, if so, would I be
able to see the User ID so I can use it in the pivot query?
To answer on your question: You could use arguments in scope, e.g:
$discounts = User::find(1)->discounts()->active(true)->get(); // within date range
$active = Discount::active()->get();
Scope:
public function scopeActive($query, $withDates = false) {
return $query
->where('active', true)
->when($withDates, function($query){
$query
->wherePivot('start', '>=', Carbon::now())
->wherePivot('end', '<=', Carbon::now());
});
}
But I think that it is not better than your solution.
Also, you could use 2 different scopes.
sorry for the title of this question but I am not sure how to ask it...
I am working on a project where I have two Models Trains and Cars, to this model I have a belonging Route.
I want to make a query and check if the routeable_type is App\Car than with the selected routeable_id to get the data from the Car. And if the routeable_type is Train then with the ID to get the data from the Tran.
So my models go like this:
Train:
class Train extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Car:
class Car extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Route:
class Route extends Model
{
public function routeable()
{
return $this->morphTo();
}
}
And the query I have at the moment is:
$data = Route::leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')
->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')
->select('routes.id', 'cars.model AS carmodel', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
With this query if I have the same ID in cars and trains I get the data from both and all messes up. How do I check if routeable_type is Car ... do this, if routeable_type is Train .. do that?
Will something like this be possible in a 1 single query:
$data = Route::select('routes.id', 'routeable_type', 'routes.created_at');
if(routeable_type == 'Car'){
$data = $data->leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')->select('routes.id', 'cars.model AS carmodel', 'routeable_type', 'routes.created_at');
}else{
$data = $data->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')->select('routes.id', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
}
Maybe this is what you are looking for?
DB::table('routes')
->leftJoin('cars', function ($join) {
$join->on('cars.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Car');
})
->leftJoin('trains', function ($join) {
$join->on('trains.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Train');
})
->select('routes.id', 'cars.model AS car_model', 'trains.model AS train_model', 'routes.routeable_type', 'routes.created_at');
->get();
I think you may want to follow the morphedByMany design.
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many-polymorphic-relations
This was also a neat visual for the different relation types.
https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
I was faced with a similar issue though I failed to follow the correct design initially and was forced to query the many possible relations then wrote custom logic after to collect the relation types and ids then do another query and assign them back through iteration. It was ugly but worked... very similar to how Eloquent does things normally.
i don't have enough repo, so i can't comment. that's why i am putting as an answer.
You should use 2 different queries, for each model.
This will be better, code wise as well as performance wise. also if both models have similar fields you should merge them to 1 table and add a 'type' column.
and put non-similar fields in a 'meta' column.
( in my opinion )
I have two tables 'purchases' and 'accounts_purchase_history'. purchase has many accounts history. So I need to get all the purchases with the latest history.
What i have done so far
$purchases = Purchases::with(['accounts_purchase_historie' => function($query){
return $query->orderBy('id','DESC')->first();
}])->orderBy('id','DESC')->where('status','extended')->get();
But this query only gets the first purchase history, How can i solve this?
You can use a HasOne relationship:
public function latest_history() {
return $this->hasOne(...)->orderBy('id','DESC');
}
$purchases = Purchases::with('latest_history')
->orderBy('id','DESC')
->where('status','extended')
->get();
foreach($purchases as $purchase) {
$purchase->latest_history
}
However, this will still fetch all the histories from the database in the background.