Restricting hasMany relationship where child.field != parent.field without joins - laravel

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

Related

How to return collection in reverse order

In my Conversation model I have
protected $with = ['messages', 'users'];
How would I change the order that it returns the 'messages' to DESC?
Usual Eloquent relationship looks something like this:
class Category extends Model
{
public function products()
{
return $this->hasMany('App\Product');
}
}
So here’s what we should do to automatically order products by title in every query that uses this relationship:
public function products()
{
return $this->hasMany('App\Product')->orderBy('name');
}
That’s it, everything is “in order” now!
Source: https://laraveldaily.com/eloquent-relationships-with-automatic-orderby/
Try this on your controller, it will order messages in descending order
User::with(['messages' => function($query) {
$query->orderBy('id', 'DESC');
}])->get();
for more see documentation

Order on second degree relationship in Eloquent

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!

Merge two hasOne relation in laravel - Eloquent

I have two table one is product and secound is RelatedProduct
where related product is connected with products.id and sometime with product.parent_id
public function relationWithId()
{
return $this->hasOne('App\RelatedProduct', 'product_id', 'id');
}
public function relationWithParent()
{
return $this->hasOne('App\RelatedProduct', 'product_id', 'parent_id');
}
so need a third relation where I can merge above both like this
public function relationProduct()
{
/*****/
}
Product::where('status', '=', 'active')->with('relationProduct')->get();
thanks in advance
Try creating the accessor which merges the two relationship and returns the collections.
public function getRelatedProductAttribute($value){
$withId = $this->relationWithId;
$withParentId = $this->relationWithParent;
return $withId->push($withParentId);
}

Laravel: how to get count() of all relations NOT collection

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.

Laravel: One to Many to Many, retrieve distinct() values

Laravel 4 Project, using Eloquent ORM.
I have three tables: customers, orders and products (+ 1 pivot table order_product). Customers are linked one-to-many to Orders. Orders are linked many-to-many to Products.
Customers 1-->N Orders N<-->N Products
I would like to have a method on Customer model that retrieves a list of products that customer is buying.
To better understand this, assume products are consumable.
For example Customer #1 can place:
Order #1 for Products A, B and C;
Order #2 for Products A, C and D;
Order #3 for Products C and E;
...and the result I want to retrieve is a Collection with Products A, B, C, D and E.
Models are (pseudo-coded on the fly):
class Product extends Eloquent {
public function orders()
{
return $this->belongsToMany('Order');
}
}
class Orders extends Eloquent {
public function customer()
{
return $this->belongsTo('Customer', 'customer_id');
}
public function products()
{
return $this->belongsToMany('Product');
}
}
class Customers extends Eloquent {
public function orders()
{
return $this->hasMany('Orders', 'customer_id');
}
public function products()
{
// What to put here ???
}
}
Thanks to #deczo's answer, I was able to put up a single query method to retrieve items:
public function items()
{
$query = DB::table('items')->select('items.*')
->join('item_order', 'item_order.component_id', '=', 'items.id')
->leftJoin('orders', 'item_order.order_id', '=', 'orders.id')
->leftJoin('customers', 'customers.id' , '=', 'orders.customer_id')
->where('customers.id', $this->id)
->distinct()
->orderBy('items.id');
$eloquent = new Illuminate\Database\Eloquent\Builder( $query );
$eloquent->setModel( new Item );
return $eloquent->get();
}
This is a Many-to-Many relationship, but with the Orders table as the pivot table.
class Customers extends Eloquent {
public function orders()
{
return $this->hasMany('Orders', 'customer_id');
}
public function products()
{
return $this->belongsToMany('Products', 'orders', 'customer_id', 'product_id');
}
}
I've included the last two parameters, but if you follow the singular_id pattern they can be left out.
It's possible to receive distinct Product models like this:
public function products()
{
return $this->belongsToMany('Products', 'orders', 'customer_id', 'product_id')
->distinct();
}
#deczo's answer probably works fine, and is probably a lot more performant as all the data reduction is done in the database itself, but here's a 'pure Laravel' way that's undoubtedly more readable:
use Illuminate\Database\Eloquent\Collection;
class Customer extends Eloquent
{
...
public function products()
{
$products = new Collection;
foreach ($this->orders as $order) {
$products = $products->merge($order->products);
}
return $products;
}
}
Note that this method will not act like normal relationship methods - to get the resulting collection you call the method (i.e. $products = $customer->products();) and you can't access it as a property like you can with relationships (i.e. you can't do $products = $customer->products;).
Also, I'm kinda going on my understanding of the Illuminate\Database\Eloquent\Collection#merge() method here that it automatically does a DISTINCT-like thing. If not, you'll have to do a $collection->unique() kinda thing.
I can't think of easy relation method for this one, but here's a workaround:
$productsIds = DB::table('customers')
->leftJoin('orders', 'orders.customer_id', '=', 'customers.id')
->join('order_item', 'order_item.order_id', '=', 'orders.id')
->leftJoin('items', 'order_item.item_id' , '=', 'items.id')
->distinct()
->get(['items.id']);
$productsIds = array_fetch($productsIds, 'id');
$productsCollection = Product::whereIn('id', $productsIds);

Resources