Bulk updates of related models - laravel

Update single...
$modelA->update(['status_id' => 1])
Update multiple...
ModelA::whereIn('id', $data['ids'])->update(['status_id' => 1])
SO
Updating a model related to a single row can work as follows...
$modelA->relatedModel()->update(['status_id' => 1])
($modelA instance is supplied via route model binding, and the relatedModel() method/relationship is defined in the "ModelA" model)
BUT
When attempting the following,
ModelA::whereIn('id', $data['ids'])->relatedModel()->update(['status_id' => 1])
I get an error
Call to undefined method Illuminate\Database\Eloquent\Builder::relatedModel()
It seems that I am not really understanding what is being returned by
ModelA::whereIn('id', $data['ids'])
So what I'm looking for is a method of updating the related models of multiple rows (obviously without a loop of any kind).

whereIn() and many other methods, like where(), has(), with(), etc. returns a Builder instance, which lets you chain additional queries. You cannot access ModelA instance methods, like relatedModel() on a Builder instance.
You will need to call ->get(), and iterate the results calling relatedModel()->update():
foreach(ModelA::whereIn('id', $data['ids'])->get() as $modelA) {
$modelA->relatedModel()->update(['status_id' => 1]);
}
Alternatively, if you'd like a single-liner without a loop, you will need to query the ModelB model (or whatever relatedModel() is) directly:
ModelB::whereIn('model_a_id', $data['ids'])->update(['status_id' => 1]);
Note: Assumed that ModelB has a model_a_id. If it doesn't, you can use the whereHas() method instead:
ModelB::whereHas('modelA', function ($query) use ($data) {
return $query->whereIn('model_a.id', $data['ids'];
})->update(['status_id' => 1]);
Note: Again, made some assumptions on your table/column names; adjust as required.

Related

What is the different between laravel WITH and LOAD [duplicate]

I really tried to understand the difference between the with() method and the load() method, but couldn't really understand.
As I see it, using the with() method is "better" since I eager load the relation. It seems that if I use load() I load the relation just as if I would use the hasMany() (or any other method that relates to the relation between objects).
Do I get it wrong?
Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
"Eager" here means that we're associating all the related models for a particular result set using just one query, as opposed to having to run n queries, where n is the number of items in the initial set.
Eager loading using with()
If we eager load using with(), for example:
$users = User::with('comments')->get();
...if we have 5 users, the following two queries get run immediately:
select * from `users`
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with a collection of models that have the comments attached to the user model, so we can do something like $users->comments->first()->body.
"Lazy" eager loading using load()
Or, we can separate the two queries, first by getting the initial result:
$users = User::all();
which runs:
select * from `users`
And later, if we decide that we need the related comments for all these users, we can eager load them after the fact:
$users = $users->load('comments');
which runs the 2nd query:
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with the same result, just split into two steps. Again, we can call $users->comments->first()->body to get to the related model for any item.
Why use load() vs. with()? load() gives you the option of deciding later, based on some dynamic condition, whether or not you need to run the 2nd query. If, however, there's no question that you'll need to access all the related items, use with().
The alternative to either of these would be looping through the initial result set and querying a hasMany() relation for each item. This would end up running n+1 queries, or 6 in this example. Eager loading, regardless of whether it's done up-front with with() or later with load(), only runs 2 queries.
As #damiani said, Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
There is one more difference between With() & load(), you can put the conditions when using with() but you can't do the same in case of load()
For example:
ProductCategory::with('children')
->with(['products' => function ($q) use($SpecificID) {
$q->whereHas('types', function($q) use($SpecificID) {
$q->where('types.id', $SpecificID)
});
}])
->get();
#damiani Explanied difference between load() and with() as well but he said load() is not cacheable so I wanna say couple words about it.
Let assume we have a blog post and related with comments. And we're fetching together and caching it.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->with("comments")->first();
});
But if there is a new comment and we want to display it immediately, we have to clear post cache and fetch post and comments together again. And that causes unnecessary queries. Lets think there are another queries for tags, media, contributors of the post etc. it will increase amount of resource usage..
public function load($relations)
{
$query = $this->newQueryWithoutRelationships()->with(
is_string($relations) ? func_get_args() : $relations
);
$query->eagerLoadRelations([$this]);
return $this;
}
As you can see above when we use the method it loads given relation and returns model with fetched relation. So you can return it outside of a callback.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->first();
});
$post = Cache::remember("post.relation.images.".$slug,720,function()use($post){
return $post->load("images");
});
$post = Cache::remember("post.relation.comments".$slug,720,function()use($post){
return $post->load("comments");
});
So if we load them seperatly, next time when some of them updated all you need to do clear specific relation cache and fetch it again. No need to fetch post, tags, images etc. over and over.

Error Call to a member function where() on array Laravel

I want to do filtering from the data that I display, but there is a problem when I add where to my data.
the plan in the future I want to add if isset $request name, date and others. but was constrained at this one point.
Thank you for helping to answer in advance
$matchs =Matchs::where('type', 'sparring')->where('status','Pending')->whereNull('deleted_at')->get()->toArray();
$data=[];
foreach ($matchs as $key) {
$lawan = Matchs::where('id', $key['id'])->first()->ToArray();
$pertandingan = Sparring::where('match_id', $key['id'])->first()->ToArray();
$dua_arah = MatchTwoTeam::where('match_id', $key['id'])->first()->ToArray();
$tim = Team::where('id', $dua_arah['home_team'])->first()->ToArray();
$transfer['name']=$tim['name'];
$transfer['city']=$lawan['city'];
$transfer['field_cost']=$pertandingan['field_cost'];
$transfer['referee_cost']=$pertandingan['referee_cost'];
$transfer['logo_path']=$tim['logo_path'];
$transfer['nama_lapangan']=$lawan['nama_lapangan'];
$transfer['date']=$lawan['date'];
array_push($data,$transfer);
array_push($data,$pertandingan);
}
$data->where('name', 'LIKE', '%'.'football'.'%')->get()->toArray();
$data = array_search('football', array_column($data, 'name'));
$tittle="Sparring";
return view('mode.sparring',[
'tittle' => $tittle,
'data' => $data,
]);
You are trying to call where in an array which is not possible.
As you can see in the first line of your code you are calling where method in your model class. Like Matchs::where('type', 'sparring'), this is possible because Matchs is a Model class.
Now you can run where even if you are using array. You can convert that day in collection and then use array on that collection.
As below:
collect($data)->where('name', 'football')->toArray();
Here collect() will convert the $data array to collectio and then run the where() method in collectio then toArray() will change it back to array. But unfortunately there is no like operator possible in collection class. See the list of available method in Laravel collection here: https://laravel.com/docs/8.x/collections#available-methods
There is a way to do what you are trying to do. As far as I understand you want to filter the Matches where the Team name has footbal in it. You can do it like this:
Matchs::where('type', 'sparring')
->where('status','Pending')
->whereNull('deleted_at')
->whereHas('team', function($team) {
return $team->where('name', 'LIKE', '%'.'football'.'%')
})
->get()
->toArray();
So, here we can get the only those Mathes that has the Team that has the name contains football.
Few suggestion for you as seems you are new in Laravel:
Model name should be singular instead of plural, so the model class Matchs should be Match. Your name for team's model is Team is correct.
Avoid using toArray() because you won't need it. When you call get() it will return object of collection which more readable and powerful then array in most cases.
The code I suggested to use the like using whereHas will only work if you have propery defined your team relation in your Matchs class. So, defining your relationships in model is also important. If you do so, you don't even need the for loop and all those where in other model in that loop. You can do it in one query with all the relationships.

laravel withsum with additional where clause

I know you can do a withSum on a relationship like this:
$posts = Post::withSum('comments', 'votes')->get();
But I like to chain an additional where clause on this, something like this (doesn't work but as an example):
$posts = Post::withSum('comments', 'votes', function (Builder $query) {
$query->where('comments.votes', '>', 5);
})->get()
Is that possible?
While Aggregating Related Models is not explicit about this in the documentation, it does give us a clue by mentioning:
If you need to set additional query constraints on the count query, you may pass an array keyed by the relationships you wish to count. The array values should be closures which receive the query builder instance.
If you need to set additional query constraints on the count query, you can pass an encoded array by the relationships you want to count. The values in the array must be anonymous functions that receive the query builder instance.
So, we can take advantage of that anonymous function to modify the query builder instance so that instead of taking the default values (which would use the aggregate 'count'), and pass it the subquery we want to do, which is the where clause and the 'sum' of the column.
Said that, your query cuold looks like:
Post::withCount(['comments as comments_sum_votes' => function($query) {
$query->where('comments.votes', '>', 5)->select(DB::raw('sum(votes)'));
}])->get()

How to dissociate elements from a HasMany relation?

There's the save and saveMany methods on the HasMany relation class, but where are the dissociate(Many)/detach(Many) methods? There's also no built-in way to get the inverse relationship method, so what's the best way to dissociate an array of id's/models from a HasMany relationship object.
Currently I'm using:
$hasMany = $parent->theRelationship(); // Get the relationship object.
$child = $hasMany->getRelated(); // Get an empty related model.
$key = $hasMany->getForeignKeyName(); // Get the name of the column on the child to set to NULL.
$child->findMany($IDs)->each(function($model) use ($key) {
$model->$key = NULL;
$model->save();
});
This could be alot shorter with something like:
$hasMany = $parent->theRelationship();
$hasMany->dissociate($IDs);
Bonus points if you have any official answers from Taylor as to why he hasn't implemented this, I've seen him close feature requests of this kind on GitHub.
I am not sure why there isn't a function, but to be more performant than your example, you could use the DB class like:
\DB::table('child_table')->where('parent_id', $parent->id)->update(['parent_id' => null]);
You could use detach like so;
$parent->theRelationship()->detach([1,2,3])
Where you pass an array of IDs.
From Laravel documentation:
"For convenience, attach and detach also accept arrays of IDs as input"
The performatic way (1 db update):
$partent->theRelationship()->update(['parent_id' => null]);
The readable way (multiple db updates):
$parent->theRelationship->each->parentRelationship()->dissociate();

laravel with() method versus load() method

I really tried to understand the difference between the with() method and the load() method, but couldn't really understand.
As I see it, using the with() method is "better" since I eager load the relation. It seems that if I use load() I load the relation just as if I would use the hasMany() (or any other method that relates to the relation between objects).
Do I get it wrong?
Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
"Eager" here means that we're associating all the related models for a particular result set using just one query, as opposed to having to run n queries, where n is the number of items in the initial set.
Eager loading using with()
If we eager load using with(), for example:
$users = User::with('comments')->get();
...if we have 5 users, the following two queries get run immediately:
select * from `users`
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with a collection of models that have the comments attached to the user model, so we can do something like $users->comments->first()->body.
"Lazy" eager loading using load()
Or, we can separate the two queries, first by getting the initial result:
$users = User::all();
which runs:
select * from `users`
And later, if we decide that we need the related comments for all these users, we can eager load them after the fact:
$users = $users->load('comments');
which runs the 2nd query:
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with the same result, just split into two steps. Again, we can call $users->comments->first()->body to get to the related model for any item.
Why use load() vs. with()? load() gives you the option of deciding later, based on some dynamic condition, whether or not you need to run the 2nd query. If, however, there's no question that you'll need to access all the related items, use with().
The alternative to either of these would be looping through the initial result set and querying a hasMany() relation for each item. This would end up running n+1 queries, or 6 in this example. Eager loading, regardless of whether it's done up-front with with() or later with load(), only runs 2 queries.
As #damiani said, Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
There is one more difference between With() & load(), you can put the conditions when using with() but you can't do the same in case of load()
For example:
ProductCategory::with('children')
->with(['products' => function ($q) use($SpecificID) {
$q->whereHas('types', function($q) use($SpecificID) {
$q->where('types.id', $SpecificID)
});
}])
->get();
#damiani Explanied difference between load() and with() as well but he said load() is not cacheable so I wanna say couple words about it.
Let assume we have a blog post and related with comments. And we're fetching together and caching it.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->with("comments")->first();
});
But if there is a new comment and we want to display it immediately, we have to clear post cache and fetch post and comments together again. And that causes unnecessary queries. Lets think there are another queries for tags, media, contributors of the post etc. it will increase amount of resource usage..
public function load($relations)
{
$query = $this->newQueryWithoutRelationships()->with(
is_string($relations) ? func_get_args() : $relations
);
$query->eagerLoadRelations([$this]);
return $this;
}
As you can see above when we use the method it loads given relation and returns model with fetched relation. So you can return it outside of a callback.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->first();
});
$post = Cache::remember("post.relation.images.".$slug,720,function()use($post){
return $post->load("images");
});
$post = Cache::remember("post.relation.comments".$slug,720,function()use($post){
return $post->load("comments");
});
So if we load them seperatly, next time when some of them updated all you need to do clear specific relation cache and fetch it again. No need to fetch post, tags, images etc. over and over.

Resources