I have connected tables with a hasMany & belongsTo relationship.
Bridal Model:
public function plans()
{
return $this->hasMany("App\Plan");
}
Plan Model:
public function bridal()
{
return $this->belongsTo("App\Bridal");
}
And I have an query that returns those data to endpoint.
public function bridal()
{
$bridals = Bridal::with(["plans" => function($query){
$query->orderBy("plans.plan_price");
}])
->groupBy("place_name")
->get();
return $bridals;
}
Everything is fine, except one thing. In Bridal table some of ID doesn't have plan. So when I return datas, some of bridal id comes with an empty Plans array.
I want to prevent that. If a bridal id doesn't have a plan, then I don't want to return that bridal id. How can I achieve what I want here?
You can use Has. If you just use has('relation') that means you only want to get the models that have at least one related model in this relation.
$bridals = Bridal::with(["plans" => function($query){
$query->orderBy("plans.plan_price");
}])->has('plans')
->groupBy("place_name")
->get();
Check more info SO answer
Add whereHas() in your query : so you will get bridal records which has plans
$bridals = Bridal::with(["plans" => function($query){
$query->orderBy("plans.plan_price");
}])
->whereHas('plans')
->groupBy("place_name")
->get();
Related
I'm trying to generate the statistics based on a polymorphic relationship. Each database which it can relate to has an identifier column, so I would like to ideally group the data by identifier (once I have the rest working).
I believe you can't use the with function on a polymorphic relationship, but I have attached my code because I think it illustrates what I'm trying to do quite well.
OrderItem MorphTo function
public function item()
{
return $this->morphTo('item');
}
Statistics generator
public function groupOrderCountByType()
{
return OrderItem::with(['item' => function($query){
$query->select(DB::raw('`identifier` AS `application_type`'));
}])->select(DB::Raw('COUNT(`id`) AS `total`'))
->with(['order' => function($query){
$query->where('paid', true);
}])
->whereBetween('created_at', [$this->from, $this->to])
->get();
}
In my application there are users making pictures of items. The relationship structure is as follows:
Categories -> SubCategories -> Items -> Pictures -> Users
Now, there's also a shortcut relationship called itemPics between categories <--> pictures so that the number of pictures uploaded by a user can be quickly counted per category using withCount().
These are the relationships on the Category model:
public function subcategories()
{
return $this->hasMany('App\SubCategory');
}
public function items()
{
return $this->hasManyThrough('App\Item', 'App\SubCategory');
}
public function itemPics()
{
return $this->hasManyThrough('App\Item', 'App\SubCategory')
->join('pictures','items.id','=','pictures.item_id')
->select('pictures.*');
}
My problem is getting the number of pictures that a user has gathered per category. The itemPics_count column created by withCount() always has the same value as items_count, even though the number of related models for both relations given by with() are different in the JSON output.
$authorPics = Category::with(['SubCategories', 'SubCategories.items' => function ($q) use ($author_id) {
$q->with(['pictures' => function ($q) use ($author_id) {
$q->where('user_id', $author_id);
}]);
}])
->with('itemPics') /* added this to check related models in output */
->withCount(['items','itemPics'])
->get();
dd($authorPics);
This does not make sense to me. Any help is greatly appreciated.
Withcount() function is not work properly if relations include join. it works only table to table relations.
public function itemPics()
{
return $this->hasManyThrough('App\Item', 'App\SubCategory')
->select('pictures.*');
}
This solution was worked for me:
//...
public function itemPics()
{
return $this->hasManyThrough('App\Item', 'App\SubCategory');
}
Then you can do something like this:
$authorPics = Category::with(['SubCategories', 'SubCategories.items' => function ($q) use ($author_id) {
$q->with(['pictures' => function ($q) use ($author_id) {
$q->where('user_id', $author_id);
}]);
}])
->with('itemPics') /* added this to check related models in output */
->withCount(['items','itemPics' => function($query){
$query->join('pictures','items.id','=','pictures.item_id')
->select('pictures.*');
}])
->get();
dd($authorPics);
Link to more information about Laravel withCount function here https://laravel.com/docs/8.x/eloquent-relationships#counting-related-models
I have a 'user' table that has a pivot table for services that a user offers:
// App\User
public function services()
{
return $this->hasMany('App\ServiceUser');
}
On the ServiceUser model I then have another relationship to get the service information:
public function service()
{
return $this->hasOne('App\Service', 'id');
}
When fetching a team (using Laravel Spark) the query I am using is:
Team::with('users')->withUserCustomerServices()->where('id', $teamId)->first();
The scope for this query is in the Team model:
public function scopeWithCustomerServices($query)
{
$query = $query;
$query->with('users.services');
$query->with(['users.services.service' => function($q) {
$q->where('visible_to_customers', 1);
}]);
return $query;
}
When outputting (using Vue.js):
{{ user.services.length }}
I get (in this example) 6 results returned. However, one of the services has a database field 'visible_to_customers' set to 0.
Initially I thought my query would work as expected and only return 5 services however it actually still returns them all, but doesn't return the relationship (service) if the field is 0.
How can I can I only return the pivot table result where the relationship has a certain field value?
EDIT
I have updated the query to use a whereHas on the first nested relation:
$query->with(['users.services' => function($q) {
$q->whereHas('service', function($q) {
$q->where('visible_to_customers', 1);
});
}]);
This works great, only returns the pivot table rows where the services table has a field value of 1 for visible_to_customers.
However, that doesn't fetch the related row itself.
If I then chain on:
$query->with(['users.services' => function($q) {
$q->whereHas('service', function($q) {
$q->where('visible_to_customers', 1);
});
}]);
$query->with(['users.services.service' => function($q) {
$q->where('visible_to_customers', 1);
}]);
It remains the same issue where it fetch all of the rows but then only the related rows where the field is 1.
Fixed this issue by using a where has on the first relationship that is the pivot:
$query->with(['users.services' => function($q) {
$q->whereHas('service', function($q) {
$q->where('visible_to_customers', 1);
})->with('service');
}]);
I then appended the ->with('service') to the end of the chain.
I created a relationship between the "Review, Games and Info" tables, unfortunately, though, the main table is Games, and he orders me all for Games, While I would like to order the ID of "review" table.
Models: Review
public function games()
{
return $this->hasMany('App\Models\Giochi', 'id_gioco', 'id');
}
Models: Giochi
public function review()
{
return $this->belongsTo('App\Models\Review', 'id', 'id_gioco');
}
public function infogiochi()
{
return $this->belongsTo('App\Models\InfoGiochi', 'id', 'id_gioco');
}
Models: InfoGiochi
public function games()
{
return $this->hasMany('App\Models\Giochi', 'id', 'id_gioco');
}
Controller:
$review = Giochi::with('Review','InfoGiochi')->orderBy('id','DESC')->get();
Here is a screenshot of my json:
How do I order content for review table IDs?
You have 2 options. You use a join and order in the sql statement or you order it after retrieving the results in the collection.
Using Join
Giochi::select('giocos.*')
->with('Review','InfoGiochi')
->leftJoin('reviews', 'reviews.id', '=', 'giocos.id_gioco')
->orderBy('reviews.id','DESC')
->get();
Sorting Collection
Giochi::with('Review','InfoGiochi')
->get()
->sortByDesc(function($giochi) {
return $giochi->review->id;
});
This would be the shortest version to sort on the collection:
Giochi::with('review')
->get()
->sortByDesc('review.id');
You can modify your relationship query when you fire it:
Giochi::with([
'Review' => function ($query) { return $query->orderBy('id','DESC'); },
'InfoGiochi'
])->orderBy('id','DESC');
You can try with a raw query or you can use ->orderBy() directly on review function.
I have 3 models
User
Channel
Reply
model relations
user have belongsToMany('App\Channel');
channel have hasMany('App\Reply', 'channel_id', 'id')->oldest();
let's say i have 2 channels
- channel-1
- channel-2
channel-2 has latest replies than channel-1
now, i want to order the user's channel by its channel's current reply.
just like some chat application.
how can i order the user's channel just like this?
channel-2
channel-1
i already tried some codes. but nothing happen
// User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies'])
->orderBy('replies.created_at'); // error
}
// also
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies' => function($qry) {
$qry->latest();
}]);
}
// but i did not get the expected result
EDIT
also, i tried this. yes i did get the expected result but it would not load all channel if there's no reply.
public function channels()
{
return $this->belongsToMany('App\Channel')
->withPivot('is_approved')
->join('replies', 'replies.channel_id', '=', 'channels.id')
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'ASC');
}
EDIT:
According to my knowledge, eager load with method run 2nd query. That's why you can't achieve what you want with eager loading with method.
I think use join method in combination with relationship method is the solution. The following solution is fully tested and work well.
// In User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved');
}
public function sortedChannels($orderBy)
{
return $this->channels()
->join('replies', 'replies.channel_id', '=', 'channel.id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Then you can call $user->sortedChannels('desc') to get the list of channels order by replies created_at attribute.
For condition like channels (which may or may not have replies), just use leftJoin method.
public function sortedChannels($orderBy)
{
return $this->channels()
->leftJoin('replies', 'channel.id', '=', 'replies.channel_id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Edit:
If you want to add groupBy method to the query, you have to pay special attention to your orderBy clause. Because in Sql nature, Group By clause run first before Order By clause. See detail this problem at this stackoverflow question.
So if you add groupBy method, you have to use orderByRaw method and should be implemented like the following.
return $this->channels()
->leftJoin('replies', 'channels.id', '=', 'replies.channel_id')
->groupBy(['channels.id'])
->orderByRaw('max(replies.created_at) desc')
->get();
Inside your channel class you need to create this hasOne relation (you channel hasMany replies, but it hasOne latest reply):
public function latestReply()
{
return $this->hasOne(\App\Reply)->latest();
}
You can now get all channels ordered by latest reply like this:
Channel::with('latestReply')->get()->sortByDesc('latestReply.created_at');
To get all channels from the user ordered by latest reply you would need that method:
public function getChannelsOrderdByLatestReply()
{
return $this->channels()->with('latestReply')->get()->sortByDesc('latestReply.created_at');
}
where channels() is given by:
public function channels()
{
return $this->belongsToMany('App\Channel');
}
Firstly, you don't have to specify the name of the pivot table if you follow Laravel's naming convention so your code looks a bit cleaner:
public function channels()
{
return $this->belongsToMany('App\Channel') ...
Secondly, you'd have to call join explicitly to achieve the result in one query:
public function channels()
{
return $this->belongsToMany(Channel::class) // a bit more clean
->withPivot('is_approved')
->leftJoin('replies', 'replies.channel_id', '=', 'channels.id') // channels.id
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'desc');
}
If you have a hasOne() relationship, you can sort all the records by doing:
$results = Channel::with('reply')
->join('replies', 'channels.replay_id', '=', 'replies.id')
->orderBy('replies.created_at', 'desc')
->paginate(10);
This sorts all the channels records by the newest replies (assuming you have only one reply per channel.) This is not your case, but someone may be looking for something like this (as I was.)