Laravel: How to write a join count query on belongsToMany relationships? - laravel

I got the following:
User, Role, with a role_user pivot table and a belongsToMany
relationship
User, Location, with a location_user pivot table and a belongsToMany relationship
There's 2 roles for the user: owner & gardener
Location has a 'gardeners_max' field
In model Location:
protected $appends = ['is_full'];
public function getIsFullAttribute()
{
return $this->attributes['name'] = $this->remainingGardeners() <= 0;
}
public function countGardeners()
{
return $this->gardeners()->count();
}
public function remainingGardeners()
{
return $this->gardeners_max - $this->countGardeners();
}
Now, doing that :
Location::all();
I get that :
[
{
name: 'LocationA',
gardeners_max: 3,
owners: [...],
garderners: [...]
...
is_full: false
}
]
which is cool. BUT... it's not possible to do a WHERE clause on the appended attribute.
Location::where('is_full',true)->get() // Unknown column 'is_full' in 'where clause'
So i'd like to write a join query so I can do a where clause on is_full
And I just can't find the way. Any help will be greatly appreciated!
IMPORTANT:
I know the filter() method to get the results but I need to do a single scopeQuery here

You could try to manipulate the Collection after loading the object from database:
Location::get()->where('is_full', true)->all();
(You have to use get first then all, not sure it works otherwise)
Not sure it's optimized thought.

You can make scope in your location model like this
public function scopeFull(Builder $query)
{
return $query->where('is_full', true);
}
Now you just get all location like this
Location::full()->get();

Related

Get only one column from relation

I have found this: Get Specific Columns Using “With()” Function in Laravel Eloquent
but nothing from there did not help.
I have users table, columns: id , name , supplier_id. Table suppliers with columns: id, name.
When I call relation from Model or use eager constraints, relation is empty. When I comment(remove) constraint select(['id']) - results are present, but with all users fields.
$query = Supplier::with(['test_staff_id_only' => function ($query) {
//$query->where('id',8); // works only for testing https://laravel.com/docs/6.x/eloquent-relationships#constraining-eager-loads
// option 1
$query->select(['id']); // not working , no results in // "test_staff_id_only": []
// option 2
//$query->raw('select id from users'); // results with all fields from users table
}])->first();
return $query;
In Supplier model:
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id')
//option 3 - if enabled, no results in this relation
->select(['id']);// also tried: ->selectRaw('users.id as uid from users') and ->select('users.id')
}
How can I select only id from users?
in you relation remove select(['id'])
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id');
}
now in your code:
$query = Supplier::with(['test_staff_id_only:id,supplier_id'])->first();
There's a pretty simple answer actually. Define your relationship as:
public function users(){
return $this->hasMany(User::class, 'supplier_id', 'id');
}
Now, if you call Supplier::with('users')->get(), you'll get a list of all suppliers with their users, which is close, but a bit bloated. To limit the columns returned in the relationship, use the : modifier:
$suppliersWithUserIds = Supplier::with('users:id')->get();
Now, you will have a list of Supplier models, and each $supplier->users value will only contain the ID.

HasMany Relation through BelongsToMany Relation

Is it possible to make Laravel relation through belongsToMany relations?
I have 4 tables:
1)Restaurants (id , name) - uses hasManyRelation with Workers table
2)Directors (id , name)
3)Directors_Restaurants (id, director_id, restaurant_id) - pivot table for connecting belongsToMany Restaurants with Directors
3)Workers (id, name, restaurant_id)
With this function in Directors model i can get all connected restaurants
public function restaurants()
{
return $this->belongsToMany('App\Restaurant','director_restaurant');
}
With this function in my code i can get all workers of all restaurants of one director
$director = Director::find(1);
$director->load('restaurants.workers');
$workers = $director->restaurants->pluck('workers')->collapse();
So my question is : can i declare similar relation in my Director model to get all its workers of all its restaurants?
Of course you can have hasMany relationship method on Director model with Eager Loading
just like below
public function restaurants()
{
return $this->hasMany(Restaurant::class)->with('restaurants.workers');
}
i can suggest a solution like this:
Director Model OPTION 1
public function getAllRestaurants(){
return $this->hasMany(Restaurant::class)->with('restaurants.workers');
}
Director Model OPTION 2
public function getAllRestaurants(){
$this->load('restaurants.workers');
return $this->restaurants->pluck('workers')->collapse();
}
You can get all restaurants anywhere
$all_restaurants = Director::find(1)->getAllRestaurants();
You can define a direct relationship by "skipping" the restaurants table:
class Director extends Model
{
public function workers()
{
return $this->belongsToMany(
Worker::class,
'director_restaurant',
'director_id', 'restaurant_id', null, 'restaurant_id'
);
}
}
You can define an accessor method in your model to hide some of the logic
# App/Director.php
// You'll need this line if you want this attribute to appear when you call toArray() or toJson()
// If not, you can comment it
protected $appends = ['workers'];
public function getWorkersAttribute()
{
return $this->restaurants->pluck('workers')->collapse();
}
# Somewhere else
$director = Director::with('restaurants.workers')->find(1);
$workers = $director->workers;
But ultimately, you still have to load the nested relationship 'restaurants.workers' for it to work.
Given your table attributes you could also define a custom HasMany relationship that looks like this
# App/DirectorRestaurant.php
public function workers()
{
return $this->hasMany(Worker::class, 'restaurant_id', 'restaurant_id');
}
# Somewhere else
$director = Director::find(1);
$workers = DirectorRestaurant::where('director_id', $director->id)->get()->each(function($q) { $q->load('workers'); });
But I don't recommend it because it's not very readable.
Lastly, there's the staudenmeir/eloquent-has-many-deep package where you can define that sort of nested relationship.
https://github.com/staudenmeir/eloquent-has-many-deep

Laravel Nova Metrics Partition belongsToMany relationship

I have the following data model:
(Publication) <-[belongsToMany]-> (Subscriber)
I want to create a Nova Partition Metric to display the number of Subscribers for each Publication.
The calculate method of my Partition class looks like this:
public function calculate(Request $request)
{
return $this->count($request, Subscriber::with('publications'), 'publication.id');
}
But I am getting an "unknown column" error. Anyone know how to make this work?
You could do something like this:
public function calculate(Request $request)
{
$subscribers = Subscriber::withCount('publications')->get();
return $this->result(
$subscribers->flatMap(function ($subscriber) {
return [
$subscriber->name => $subscriber->publications_count
];
})->toArray()
);
}
The count helper only allows to group by a column on model's table. It also don't allow to join tables.
If you want a more complex query, with a join and a group by column in another table, you can build your own array of results and return it with the results helper.
You can see the results helper docs here: https://nova.laravel.com/docs/1.0/metrics/defining-metrics.html#customizing-partition-colors
You should create your array (you can use eloquent or query builder here) inside the calculate function, then return that array with the results helper.
Hope this helps!
You can make the groupBy on publication_foreign_key in Subscriber table and edit the publication_foreign_key to publication_name using ->label() method
Like this
public function calculate(Request $request)
{
return $this->count($request, Subscriber::class, 'publication_id')
->label(function($publicationId)
{
switch($publicationId)
{
case publication_foreign_key_1 : return publication_name_1;
break;
case publication_foreign_key_2 : return publication_name_2;
break;
default: return 'Others';
}
});
}

Count from hasManyThrough relationship with eager loading (need only Count)

I only need the count, don't want to retrive the results or perform query each time for each row. This is why I want eager loading.
I have 3 tables like the following:
Admins
id
Posts
id
admin_id
Comments
id
user_id //nullable (null if comment from admin)
admin_id //nullable (null if comment from user)
news_id
Now I want to retrieve all the posts from a single admin and all the comments count from those posts, without retrieving all the comments for posts, ONLY COUNT of comments,
With eager loading to avoid n+1 query issue;
Here I think we should make a relation to be used with eager loading like the following:
//admin model
public function commentsCountRelation()
{
return $this->hasManyThrough(Comment::class, News::class, 'admin_id', 'news_id')
->selectRaw('news_id, count(*) as count')
->groupBy('news_id');
}
--Look Here I used hasManyThrough relation because, news_id is not in Admins table.
Then I should make an attribute, to access the count easyly, like:
public function getCommentsCountAttribute()
{
return $this->commentsCountRelation->first()->count ?? 0;
}
Then access it like:
$admin = Admin::with('commentsCountRelation')->findOrFail($id);
$admin->commentsCount;
But it always returns null, why is that?
The following works for hasMany & belongsTo, I've used it on my other models like:
//relation
public function ordersCountRelation()
{
return $this->hasOne(Order::class)->selectRaw('user_id, count(*) as count')
->groupBy('user_id');
}
//attribute
public function getOrdersCountAttribute()
{
return $this->ordersCountRelation->count ?? 0;
}
//Then accessed like:
$user = User::with('ordersCountRelation')->find($id);
$user->ordersCount; //return only count
Any help will be highly appreciated
there is no need for using hasManyThrough.
just use some relations and withCount method:
$admin->posts->withCount('comments')->get();
then you can access it with: $comments_count

Laravel ORM should return Relation

Is it possible to return an value for an hasOne relation directly with an Model?
For example:
$List = Element::orderBy('title')->get();
The Element has a "hasOne" Relation to an column:
public function type()
{
return $this->hasOne('App\Type', 'id', 'type_id');
}
How can i now return automatically the "type" for the Model?
At the Moment i am looping through all Elements, and build my own "Array" of Objects, including the Key of "type" in this example. But ill prefer to do this only in my Model.
Ill know how to add a "normal" property, but can it be someone from an relation?
public function getTypeAttribute($value)
{
return // how ?
}
protected $appends = array('type');
Is this possible?
Edit:
A workaround could be to use DB:: to return the correct value - but ill dont thing thats a good workaround: like:
public function getTypeAttribute($value)
{
// make a query with $this->type_id and return the value of the type_name
}
protected $appends = array('type');
you need to eager load your relations when getting the Element:
$list = Element::with('type')->orderBy('title')->get();
then access the type using
foreach ($list as $item) {
echo $item->type->type_name;
}
where type_name would be the name of a column in the types table
Make a query scope and in that scope, join your attributes from other tables.
http://laravel.com/docs/5.1/eloquent#query-scopes
http://laravel.com/docs/5.1/queries#joins

Resources