I'm trying to do a mass update on an eloquent collection.
So I have my query, which looks a bit like this:
\Responder::with('details')
->where('job_number', $project->job_number)
->where('batch_id', ((int) $batch_id) - 1)
->where('updated_at', '<=', $target_time)
->whereHas('transactions', function($q) {
$q->where('status', 'success');
}, '<', 1)
->whereHas('details', function($q) {
$q->where('email', '<>', '');
});
This query object is stored as $query (because I'm re-using it - the same reason I dont want to switch how I'm doing the query), I am then performing an update on the collection, e.g.
$query->update(array('batch_id' => $batch_id));
This works great except it updates all the 'updated_at' timestamps. Now i like the timestamps, they are used extensively elsewhere, so i cant turn them off all together but I thought I could disable them temporarily but I've tried the following:
$query->timestamps = false;
$query->update(array('email_drop_off_index' => $batch_id));
and I can confirm that doesn't work, is there a way to do this?
Any help much appreciated
timestamps = false should be made on your model, but what you are doing is setting the value on the query builder. That's why it is not being picked up.
timestamps is an instance variable so you can't set it statically, and I don't think there is a built-in way to do it from the query builder. So I suggest try instantiating the model first, then create a new query from it, like this:
$responder = new \Responder;
$responder->timestamps = false;
$query = $responder->newQuery()
->with('details')
->where('job_number', $project->job_number)
...; // the rest of your wheres
$query->update(array('email_drop_off_index' => $batch_id));
Here's a possible solution: subclass your Responder model and turn off timestamps in the subclass.
class MassUpdateResponder extends Responder
{
public $timestamps = false;
}
Then use your new class to do the updates. This seems like a bit of a hack, but it should work.
BTW, doing an update like the following worked for me:
$query->timestamps = false;
$query->value = "new value";
$query->save();
The update() method may be doing something different that's causing it to ignore the value of $timestamps.
Related
I was trying to get the remaining time for movie delivery in a rental system made with laravel.
I built the following query using eloquent and it returned the result I wanted:
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->orderBy('Days','asc')
->paginate(6);
but there is a problem when these rentals go over the delivery deadline it returns negative values, so I would like the query to return only the rentals that have the remaining days greater than zero and then paginate those results.
I create this statement and using map() filter only the positives that are returned in a collection but the problem is that I can't paginate them.
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->get()->map(function($alquiler){
return ($alquiler->Days >= 0) ? $alquiler : null;
});
$resting_time = $resting_time->filter()->sortBy('Days');
This is the returning collection:
But this type of collection cannot be paginated.
Any idea how to fix it, or maybe an easier way to do it? Sorry if something doesn't make sense, I'm just starting in laravel.
In second case its not working,because you work with:
\Illuminate\Support\Collection::class
in first case, you work with :
\Illuminate\Database\Eloquent\Collection::class
To make it work , you can try to do next thing:
take a
\Illuminate\Support\Collection::class
and return it paginated via
Illuminate\Pagination\Paginator::class
so the end result will look like this:
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->get()->map(function($alquiler){
return ($alquiler->Days >= 0) ? $alquiler : null;
});
$resting_time = $resting_time->filter()->sortBy('Days');
return new Illuminate\Pagination\Paginator($resting_time, 6);
However, i would recommend to prepare data from SQL side, neither doing all of the manipulations from collection perspective.
Most of the answers already provided will work, but will return a collection instead of a paginated resource. The trick is to use the tap helper method before map'ping, to return the same object you modified.
return tap(Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->with('socio', 'pelicula')
->paginate(20))
->map(function ($model) {
return ($model->Days >= 0) ? $model : null;
});
or you can do this way too:
return Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->with('socio', 'pelicula')
->paginate(20))
->map(function ($model) {
if($alquiler->Days >= 0) {
return $model;
}
});
I tried both methods and it didn't work at least the way I wanted, so I did a little more research and put this together:
public function getRestingTime(){
$resting_time = Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->whereRaw('DATEDIFF(alq_fecha_hasta,NOW()) >= ?', [0])
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->orderBy('Days','asc')->paginate(6);
return $resting_time;
}
I hope it helps someone, thanks likewise to the people who responded cleared my mind a bit and gave me new things to try.
I am trying to use Eager Loading to dynamically order by relationships in Laravel.
dd(SomeModel::with(['someRelation' => function ($query) {
$query->orderByDesc('column');
})->toSql());
I'm using dd() and toSql() to try to debug what is happening and this is what I see:
"select * from "some_table" where "some_table"."deleted_at" is null"
No matter if I orderBy('column', 'ASC') or orderBy('column', 'DESC') without the dd or toSql, I get the same output as if it is ignoring the entire eager load.
Am I missing something or doing something wrong? My relation in this case looks like this:
class SomeModel
{
protected $table = 'some_table'; # for visual aid
public function someRelation(): BelongsTo
{
$this->belongsTo(SomeOtherModel::class)->select('id', 'column');
}
}
FYI, some more debug later. I attempted to try see if the function ever executes, to which it does:
SomeModel::with(['someRelation' => function ($query) {
$query->orderByDesc('column');
dd($query->toSql());
});
The dd block executes telling me the function executed and gives me:
"select "id", "name" from "some_other_table" where "some_other_table"."id" in (?, ?) and "some_other_table"."deleted_at" is null order by "name" desc"
Any help appreciated.
Update to check for subqueries SQL:
\DB::enableQueryLog();
SomeModel::with(['someRelation' => function ($test) {
$test->orderBy('name', 'DESC');
}])->get();
dd(\DB::getQueryLog());
This returns me an empty array:
[]
After reading through the full article as #TimLewis alluded to, I went with the join solution.
SomeModel::query()->select('some_table.id', 'some_table.name') # Only bring back what we need without the join
->join('some_other_table', 'some_other_table.id', '=', 'some_table.some_other_table_id')
->orderByDesc('column');
Alternatively, you can always leftJoin if you don't expect data all the time, just make some null exception handling.
I actually created an abstract class which has a global scope to do this dynamically in any Model based on a hash-map approach.
I have a search query that needs to be done. However, a search doesn't always have all values set, like in this case.
$aEvents = DB::table('events')
->where('client_id', '=', $client_id);
The question is, how can I make this where statement depend on the value of $client_id. So if the value is empty I don't want the Where statement to occur.
Also, I do not want to write several complete queries with if statements in PHP. To many variables. Ideally I'd like something like this:
$aEvents = DB::table('events')
->(($client_id != "") ? where('client_id', '=', $client_id) : "");
Using eloquent is (really!) nice and save, but I'm not yet up to speed with if statements in std Class objects I guess. Any help is appreciated.
You may try something like this:
$query = DB::table('events');
if(!empty($client_id)) {
$query->where('client_id', $client_id);
}
$aEvents = $query->get(); // Call this at last to get the result
If you are passing client_id to the server via a form/query string(user input) then you may try something like this:
if($client_id = Input::get('client_id')) {
$query->where('client_id', $client_id);
}
Update: For pagination try this:
$aEvents = $query->paginate(10); // For 10 per page
So you may call links() method in your view if you pass it like this:
return View::make('viewName')->with('aEvents', $aEvents);
In the view for pagination links:
$aEvents->links()
You can also use query scopes in the model for this purpose. Scopes allow you to easily re-use query logic in your models. In the model Event, you can add the following query scope:
public function scopeClientID($query, $client_id)
{
if ($client_id != '') {
return $query->where('client_id', '=', $client_id);
} else {
return $query;
}
}
Then from your controller or wherever you're calling it from, you can do the following:
$aEvents = Event::clientID($client_id);
If you want to get all the results, then you can do:
$aEvents = Event::clientID($client_id)->get();
Or if you want pagination, you can do:
$aEvents = Event::clientID($client_id)->paginate();
You can also chain it with other methods like you'd do in a eloquent query.
You can read more about model query scopes at http://laravel.com/docs/eloquent#query-scopes
How to merge this two queries ?
$data = DB::table('category_to_news')
->where('category_to_news.name', ucwords($category))
->remember(1440)
->count();
and
$data = DB::table('category_to_news')
->where('category_to_news.name', ucwords($category))
->remember(1440)
->get();
So, as far as I understand from your comment, you simply want to get all records from the table category_to_news and you want to know how many records are in there, right?
MySQL's count is an aggregate functions, which means: It takes a set of values, performs a calculation and returns a single value. If you put it into your names-query, you get the same value in each record. I'm not sure if that has anything to do with 'optimization'.
As already said, you simply run your query as usual:
$data = DB::table('category_to_news')
->where('name', ucwords($category))
->remember(1440)
->get(['title']);
$data is now of type Illuminate\Support\Collection which provides handy functions for collections, and one them is count() (not to be confused with the above mentioned aggregate function - you're back in PHP again, not MySQL).
So $data->count() gives you the number of items in the collection (which pretty much is an array on steroids) without even hitting the database.
Hi DB class dont return collection object it give error "call member function on array" but eloquent return collection object. for above code we can use collect helper function to make it collection instance then use count and other collection methods https://laravel.com/docs/5.1/collections#available-methods .
$data = DB::table('category_to_news')
->where('name', ucwords($category))
->remember(1440)
->get();
$data = collect($data);
$data->count();
You my get it using:
$data = DB::table('category_to_news')
->where('name', ucwords($category))
->remember(1440)
->get();
To get the count, try this:
$data->count();
Why you are using DB::table(...), instead you may use Eloquent model like this, create the model in your models directory:
class CategoryToNews extends Eloquent {
protected $table = 'category_to_news';
protected $primaryKey = 'id'; // if different than id then change it here
}
Now, you may easily use:
$data = CategoryToNews::whereName(ucwords($category))->get();
To get the count, use:
$data->count();
Please correct me if I am wrong, but I think there is no such thing as mass update in an Eloquent model.
Is there a way to make a mass update on the DB table without issuing a query for every row?
For example, is there a static method, something like
User::updateWhere(
array('age', '<', '18'),
array(
'under_18' => 1
[, ...]
)
);
(yes, it is a silly example but you get the picture...)
Why isn't there such a feature implemented?
Am I the only one who would be very happy if something like this comes up?
I (the developers), wouldn't like to implement it like:
DB::table('users')->where('age', '<', '18')->update(array('under_18' => 1));
because as the project grows, we may require the programmers to change the table name in the future and they cannot search and replace for the table name!
Is there such a static method to perform this operation? And if there is not, can we extend the Illuminate\Database\Eloquent\Model class to accomplish such a thing?
Perhaps this was not possible a few years ago but in recent versions of Laravel you can definitely do:
User::where('age', '<', 18)->update(['under_18' => 1]);
Worth noting that you need the where method before calling update.
For mass update/insert features, it was requested but Taylor Otwell (Laravel author) suggest that users should use Query Builder instead. https://github.com/laravel/framework/issues/1295
Your models should generally extend Illuminate\Database\Eloquent\Model. Then you access the entity iself, for example if you have this:
<?php
Use Illuminate\Database\Eloquent\Model;
class User extends Model {
// table name defaults to "users" anyway, so this definition is only for
// demonstration on how you can set a custom one
protected $table = 'users';
// ... code omited ...
Update #2
You have to resort to query builder. To cover table naming issue, you could get it dynamically via getTable() method. The only limitation of this is that you need your user class initialized before you can use this function. Your query would be as follows:
$userTable = (new User())->getTable();
DB::table($userTable)->where('age', '<', 18)->update(array('under_18' => 1));
This way your table name is controller in User model (as shown in the example above).
Update #1
Other way to do this (not efficient in your situation) would be:
$users = User::where('age', '<', 18)->get();
foreach ($users as $user) {
$user->field = value;
$user->save();
}
This way the table name is kept in users class and your developers don't have to worry about it.
A litle correction to #metamaker answer:
DB::beginTransaction();
// do all your updates here
foreach ($users as $user) {
$new_value = rand(1,10) // use your own criteria
DB::table('users')
->where('id', '=', $user->id)
->update(['status' => $new_value // update your field(s) here
]);
}
// when done commit
DB::commit();
Now you can have 1 milion different updates in one DB transaction
If you need to update all data without any condition, try below code
Model::query()->update(['column1' => 0, 'column2' => 'New']);
Use database transactions to update multiple entities in a bulk. Transaction will be committed when your update function finished, or rolled back if exception occurred somewhere in between.
https://laravel.com/docs/5.4/database#database-transactions
For example, this is how I regenerate materialized path slugs (https://communities.bmc.com/docs/DOC-9902) for articles in a single bulk update:
public function regenerateDescendantsSlugs(Model $parent, $old_parent_slug)
{
$children = $parent->where('full_slug', 'like', "%/$old_parent_slug/%")->get();
\DB::transaction(function () use ($children, $parent, $old_parent_slug) {
/** #var Model $child */
foreach ($children as $child) {
$new_full_slug = $this->regenerateSlug($parent, $child);
$new_full_title = $this->regenerateTitle($parent, $child);
\DB::table($parent->getTable())
->where('full_slug', '=', $child->full_slug)
->update([
'full_slug' => $new_full_slug,
'full_title' => $new_full_title,
]);
}
});
}
Laravel 6.*
We can update mass data on query as follow :
Appointment::where('request_id' , $appointment_request->id)->where('user_id', Auth::user()->id)->where('status', '!=', 'Canceled')->where('id', '!=', $appointment->id)->update(['status' => 'Canceled', 'canceled_by' => Auth::user()->id]);
Another example of working code of the mass query and mass update in same instruction:
Coordinate::whereIn('id',$someCoordIdsArray)->where('status','<>',Order::$ROUTE_OPTIMIZED)
->update(['status'=>Order::$ROUTE_OPTIMIZED]);
From Laravel 8 you can also use upsert which helped me updated multiple rows at once with each rows having different values.
https://laravel.com/docs/8.x/eloquent#upserts
laravel 5.8 you can accomplish mass update like so:
User::where('id', 24)->update (dataAssociativeArray) ;