I have two models in a many-to-many relationship: Fixture and Event.
Fixture:
public function events()
{
return $this->belongsToMany(Event::class, 'fixture_events')->withPivot('player_id');
}
Event:
public function fixtures()
{
return $this->belongsToMany(Fixture::class, 'fixture_events')->withPivot('player_id');
}
You will notice that the pivot table has an additional field player_id. This is because FixtureEvent also had a relationship to a model called Player.
FixtureEvent:
public function fixture()
{
return $this->hasOne(Fixture::class, 'id', 'fixture_id');
}
public function event()
{
return $this->hasOne(Event::class, 'id', 'event_id');
}
public function player()
{
return $this->belongsTo(Player::class, 'id', 'player_id');
}
And Player has:
public function events()
{
return $this->hasMany(FixtureEvent::class);
}
My problem arises when I want to get all the fixture_events for a player and sort them by a field in the events table. This field is named sequence.
However, whatever I do, the events always come out ordered by ID.
This is the query that I would like to order by events.sequence, whether by using some type of join or whatever works (this is inside the Player model so $this is a player object):
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})->get();
I've tried adding a join query here on fixture_events.event_id = events.id and then ordering by events.sequence but this doesn't work.
I've also tried adding orderBy directly in the model relationship, i.e. in the Fixture model:
public function events()
{
return $this->belongsToMany(Event::class, 'fixture_events')->orderBy('sequence')->withPivot('player_id');
}
But this does nothing for my problem.
How do I make this happen?
Update
At first I misread the relations, can you try with the below query?
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})->with(['events.event' => function ($query) {
$query->orderBy('sequence');
}])->get();
You have a couple of alternatives, but first I suggest you to edit your relationship to include the sequence field you are trying to load.
Then proceed with one of the following:
Order by on the relationship definition, but I think you have to load that field from the pivot table, otherwise you won't have its value, and prefix the relations table on the orderby field.
public function events() {
return $this->belongsToMany(Event::class, 'fixture_events')
->withPivot(['player_id', 'sequence'])
->orderBy('fixture_events.sequence');
}
or with:
public function events() {
return $this->belongsToMany(Event::class, 'fixture_events')
->withPivot(['player_id', 'sequence'])
->orderBy('pivot_sequence');
}
Order by a pivot field outside the relation can be done like this:
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})->with(['fixture' => function ($query) {
$query->orderBy('sequence');
}])->get();
or with:
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})
->orderBy('pivot_sequence')
->get();
Let me know if any of these methods works!
Related
I'm looking for a way to qualify a hasMany relationship to exclude/include children where the value a specific child field does/not match that of a specific parent field without using joins.
The issue with joins is that the ->select() filters out many descendant relationships (unless they are each added to the join, which is too much to manage). The descendant relationships for example would be order_item.options which is a belongsToMany of OrderItem.
Case in point:
class Order extends Model
{
public function items()
{
return $this->hasMany(OrderItem::class, 'order_id', 'id');
}
public function items_removed_by_store()
{
return $this->hasMany(OrderItem::class, 'order_id', 'id')
//->join('order', 'order_item.order_id', 'order.id')
//->where('order_item.deleted_by', '!=', 'order.customer_id')
//->select('order_item.*', 'order.customer_id')
->onlyTrashed();
}
public function items_removed_by_customer()
{
return $this->hasMany(OrderItem::class, 'order_id', 'id')
//->join('order', 'order_item.order_id', 'order.id')
//->where('order_item.deleted_by', '=', 'order.customer_id')
//->select('order_item.*', 'order.customer_id')
->onlyTrashed();
}
}
and I'm looking to query it like:
Location::has('orders.items_removed_by_customer')->get()
Get me locations where customers have removed items from their orders.
It seems like you are currently joining the relationship on its parent. Note you already have the parent data
Maybe try something like this
class Order extends Model
{
public function items()
{
// Note: it is not required to pass 'order_id', 'id' to this method as thats the default value
return $this->hasMany(OrderItem::class);
}
public function items_removed_by_store()
{
return $this->items()
->where('order_items.deleted_by', '!=', $this->customer_id)
->onlyTrashed();
}
public function items_removed_by_customer()
{
return $this->items()
->where('order_item.deleted_by', '=', $this->customer_id)
->onlyTrashed();
}
}
I have two models. Task and TaskCheck
in TaskCheck i have
class TaskCheck extends Model
{
public function task(): BelongsTo
{
return $this->belongsTo(Task::class);
}
public function owner(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function scopeOwnedBy(Builder $query, int $userId): Builder
{
return $query->where('user_id', '=', $userId);
}
}
in Task model i have
class Task extends Model
{
public function taskCheck(): HasMany
{
return $this->hasMany(TaskCheck::class)->with(['taskState', 'owner']);
}
}
And would like to use something like this:
public function scopeHasOwner(Builder $query, int $taskOwnerId): Builder
{
return $query->whereHas('taskCheck', function ($q) use ($taskOwnerId) {
$q->hasOwner($taskOwnerId);
});
}
however this throws exception Call to undefined method App\Models\Task::hasOwner() as it seems inner query is not aware of Task model.
I know I could use this instead and it works
public function scopeHasOwner(Builder $query, int $taskOwnerId): Builder
{
return $query->whereHas('taskCheck', function ($q) use ($taskOwnerId) {
$q->where('user_id', '=', $taskOwnerId);
});
}
but i would rather not repeat the where clause in every related model, because there are more related models deeper in relationships which would use similar functionality and i would like to have it on one place only.
In your TaskCheck model, you have ownedBy() scope, but you called hasOwner() in the whereHas query.
Change your query to ownedBy()
$query->whereHas('taskCheck', function ($q) use ($taskOwnerId) {
$q->ownedBY($taskOwnerId);
});
I have a model that I want to sort based on a relationship property.
First model "DeviceType":
public function make()
{
return $this->hasMany(DeviceMake::class);
}
Second model: "DeviceMake":
public function type()
{
return $this->hasOne(DeviceType::class, 'id', 'device_type_id');
}
public function model()
{
return $this->hasMany(DeviceModel::class);
}
Controller:
$type = DeviceType::with(['make'])->where('id', '=', $device_type_id)->first();
Table name is device_makes and I want to sort it by name. How can I do this?
And what about?
$type = DeviceType::with(['make' => function ($query) {
$query->orderBy('name', 'asc');
}])->where('id', '=', $device_type_id)->first();
Please note that first() will only return the first model that matches your query whereas get() will return all of them.
Maybe you could try this instead in your Model:
public function make() {
return $this->hasMany(DeviceMake::class)->orderBy('name', 'asc');
}
When I am adding a new post in my app there is a 7 tables to affect when I add single post. To fetch all posts with all post data my simple query look like:
$userPost = Post::with(['product','postattribute.attribute.category','user.userDetails'])
->offset($offset)
->limit($limit)
->whereStatus("Active")
->whereIn('product_id', $userApprovalProductIDs)
->orderBy('id','desc')
->get();
So it is retrun all data which I want. Now I want to implement search query within all tables, currently I am able to search only posts table.
If I am doing search on category table with categoryTitle I am trying to code like
where('category.title','=', $serachTitle)
But it is not working in my case.
POST model relationship :
public function user() {
return $this->belongsTo(User::class);
}
public function product() {
return $this->belongsTo(Product::class);
}
public function postattribute() {
return $this->hasMany(PostAttribute::class);
}
POSTATTRIBUTES model relationship :
public function post() {
return $this->belongsTo(Post::class);
}
public function attribute() {
return $this->belongsTo(Attribute::class);
}
ATTRIBUTES model relationship :
public function category() {
return $this->belongsTo(Category::class);
}
public function attributes() {
return $this->belongsTo(Attribute::class);
}
How can I do this ?
To apply filter on your nested relations you could use whereHas
$userPost = Post::with(['product','postattribute.attribute.category','user.userDetails'])
->offset($offset)
->limit($limit)
->whereStatus("Active")
->whereIn('product_id', $userApprovalProductIDs)
->whereHas('postattribute.attribute.category', function ($query) use($serachTitle) {
$query->where('title', '=', $searchTitle);
})
->orderBy('id','desc')
->get();
Querying Relationship Existence
From comments what i understood is you want to know how to search within each relation for a post , I already added an example to search with category title
->whereHas('postattribute.attribute', function ($query) use($var) {
$query->where('some_field_of_attribute_table', '=', $var);
})
->whereHas('postattribute', function ($query) use($var) {
$query->where('some_field_of_postattribute_table', '=', $var);
})
I have an accessor like so
public function getRegisteredCountAttribute()
{
return $this->attendees->count();
}
However, I have noticed that this counts the attendees in my collection after the query. So if my query removes some of the attendees I don't get the proper count.
Here is my query
$programs = ScheduledProgram::where('registration_start_date', '<=', $today)
->where('end_date', '>=', $today)
/* ->with(['attendees'=>function($q) use ($user_id)
{
$q->where('user_id', $user_id);
}])
->with(['scheduledProgramSegments.attendees'=>function($q) use ($user_id)
{
$q->where('user_id', $user_id);
}])
*/
->get();
I get a different number from my accessor $program->registered_count when I uncomment the comment in query section above. I guess that the accessor is giving me the count from the collection and not doing a new query to get the count I really need.
How do I get the count of registered attendees in the program?
I should note that the models attendeesand programs have a many-to-many (belongsToMany) relation with a pivot table that also has fields for registered, waitlisted.
I saw this article but I couldn't find the next belongsToMany.
Models
class ScheduledProgram extends Eloquent {
public function scheduledProgramSegments()
{
return $this->hasMany('ScheduledProgramSegment');
}
public function attendees()
{
return $this->belongsToMany('Attendee', 'prog_bookings')->withPivot('registered','paid','waitlisted');
}
public function getRegisteredCountAttribute()
{
return $this->attendees()->count();
}
}
class ScheduledProgramSegments extends Eloquent {
public function attendees()
{
return $this->belongsToMany('Attendee', 'bookings')->withPivot('paid');
}
public function scheduledProgram()
{
return $this->belongsTo('ScheduledProgram');
}
}
class ProgBooking extends Eloquent {
public function scheduled_program()
{
return $this->belongsTo('ScheduledProgram');
}
public function attendee()
{
return $this->belongsTo('Attendee');
}
}
When you fetch the $programs and eagerly load attendees with some additional constraint, those fetched filtered attendees are saved in $program->attendees attribute. When you call count() on that collection you'll get a number of attendees in that filtered collection.
If you need to count all attendees in given program you'll need to do:
public function getRegisteredCountAttribute()
{
return $this->attendees()->count();
}
Notice the additional () - as a result you'll call count() not on the eagerly loaded collection of attendees with additional constraints applied - you'll call that on the relation itself.