Can not Soft delete multiple rows using Query Builder - laravel-5

My model is something like this:
namespace App;
use Illuminate\Database\Eloquent\SoftDeletes;
class Photo extends Model {
use SoftDeletes;
protected $dates = ['deleted_at'];
}
_ I can soft delete using:
$\App\Photo::find(1)->delete();
_ It does not work when I try to use soft delete on multiple rows:
\App\Photo::whereIn('id', [1,2,3])->delete();
Does any one know why?

No, you can't soft delete multiple rows.
The only Laravel way is the DB facade in this case.
Here is how I would soft delete multiple rows.
DB::table('table_name')->whereIn('id', [array of ids])
->update([
'deleted_at' => now()
]);
or
ModelName::whereIn('id', [array of ids])
->update(['deleted_at' => now()]);
Instead of whereIn you can put in any where condition like you usually put and can update the deleted_at key. Soft delete is nothing but marking the column as deleted.
This is also a very efficient solution rather than running soft delete for each model inside a loop which can crash the system if there're too many items in the array.
Hope this helps.

The soft delete functionality only works on an instance of the Eloquent model itself. When you are doing this:
\App\Photo::find(1)->delete();
You are actually first retrieving the Photo with an ID of 1 from the database which is then prepared and made available as an instance of the Eloquent model (which can then use soft delete).
However, when you do this:
\App\Photo::whereIn('id', [1,2,3])->delete();
You are not actually retrieving anything from the database, you are basically just preparing DELETE SQL in a more convenient way. This effectively runs something like:
DELETE FROM `photos` WHERE `id` IN (1,2,3);
This is different from something like:
foreach (\App\Photo::whereIn('id', [1,2,3])->get() as $photo) {
$photo->delete(); # $photo is an eloquent model and can soft-delete
}
Notice the ->get() which is actually grabbing data from the database first and will make it available as a collection of Eloquent models (which then can soft delete).
I don't think you can soft-delete a batch. In my foreach example using ->get() I imagine multiple queries are executed - something like:
UPDATE `photos` SET `deleted_at` = NOW() WHERE `id` = 1;
UPDATE `photos` SET `deleted_at` = NOW() WHERE `id` = 2;
UPDATE `photos` SET `deleted_at` = NOW() WHERE `id` = 3;
Hope that makes sense.

Try this as well:
\App\Photo::whereIn('id', [1,2,3])
->get()
->map(function($photo) {
$photo->delete();
});

Related

How can i convert this sql query in a eloquent laravel command?

in sql query this commando do exactly i wanted:
SELECT
v.id,
(
SELECT sv.status_id
FROM status_viagem sv
WHERE sv.viagem_id = v.id
ORDER BY sv.created_at DESC LIMIT 1 ) AS status_id
FROM viagens v
Here is the sql results:
But i have no idea how can i do this using Laravel eloquent
Basically, a viagem entry can has a lot of status, but i need to get each viagem and their last status entry from status_viagem table (the pivot table)
by the way viagem/viagens means travel.
My class mapping:
class Viagem extends Model
{
...
public function status()
{
return $this->belongsToMany('App\Status')->withTimestamps();
}
...
}
class Status extends Model
{
public function viagens()
{
return $this->belongsToMany('App\Viagem')->withTimestamps();
}
}
The belongsToMany at both classes gets me a many-to-many:
can someone help me? thanks
---------- Temporary Solution -------
Thanks for all help guys. In fact i can't find a nice solution using only eloquent.
Step 1/3 - To bypass this situation i first execute the above sql to grab only the viagens under the desired status_id (last status_viagem entry):
$viagens_ids = DB::select(
"SELECT viagem_id FROM (
SELECT
v.id AS viagem_id,
(
SELECT sv.status_id
FROM status_viagem sv
WHERE sv.viagem_id = v.id
ORDER BY sv.id DESC LIMIT 1 ) AS status_id
FROM viagens v
) AS tt
WHERE tt.status_id = {$status->id}"
);
Step 2/3 - then i used the array_map to organize my viagens ids
$a = array_map(
function($obj) { return $obj->viagem_id; },
$viagens_ids
);
Step 3/3 - And at last i used elequent whereIn to fetch my viagens:
Viagem::with( 'status')->whereIn('id', $a)->get();
In fact i have solved the problem by a-old-sashion-way but i not happy with it because i wish i learn how to do it using eloquent. what bad to me.
There are many ways to query in laravel. I have created a test project for you to try. The gist are:
1. Eloquent ORM
Eloquent ORM is Laravel's magic which have some limitations in eager loading - which i just come across while contemplating your question for hours. It wont play nicely with first(), last(), and some more functions in the constrained eager loading closure.
In your case, our almost there can be fixed:
App\Models\Viagem::with(['status' => function($query){
return $query->orderBy('pivot_created_at', 'desc');
}])
->get()
It will return entire field for Viagem and Status including its pivot table (the status_viagem).
However, if you wanted to retrieve only viagem.id and status_viagem.status_id, you can map() it as such:
App\Models\Viagem::with(['status' => function($query){
return $query->orderBy('pivot_created_at', 'desc');
}])
->get()
->map(function($data){
$o = new stdClass();
$o->id = $data->id;
$o->status_id = $data->status->first()->id;
return $o;
});
Please take note that the statement above require sql query to be ran twice. Eager loading basically works by querying all the Viagem first then queries the Status and map them in memory based on the foreign keys. You can observe that replacing get with toSql will only give you the first query. Please enable Query Logging to see the second query.
2. Query Builder
Embarking from Ryan Adhitama Putra answer, you could do something like:
App\Models\Viagem::join('status_viagem', 'viagens.id', '=', 'status_viagem.viagem_id')
->orderBy('status_viagem.created_at', 'desc')
->groupBy(['status_viagem.status_id', 'viagens.id'])
->select(['viagens.id', 'status_viagem.status_id'])
->get();
This query builder approach guaranteed to be ran only once, you can replace the get() with toSql() to see the resulting query.
3. Raw Queries
Throwing DB::raw() can help sometime, but i really did not want to mention it.
I am not sure what viagens and viagem represent, but I think one of the relationships has to be belongsToMany() and the other hasMany().
then after you set relationships correctly, you can use Eloquent like this :
$status_id = Viagem::with('status')->orderBy('created_at', 'desc')->first()->pluck('status_id');
Try this.
$status_id = Viagem::join('status','viagem.id','status_viagem.viagem_id')
->select('viagem.id','status_viagem.status_id')
->get();

Laravel Eloquent increment without updating timestamps

I have an Eloquent model on which I would like to increment a single attribute. So far I've been using the following line of code to achieve this:
Thread::where('id', $threadId)->increment('like_count');
This however has the unwanted side-effect of updating the updated_at timestamp. I've found the following way of updating a record without altering the timestamp:
$thread = Thread::where('id', $threadId)->first();
$thread->timestamps = false;
$thread->like_count++;
$thread->save();
But that suddenly looks a lot less concise. Therefore, I would like to know of there's a way to use the increment method without updating timestamps.
If you do not need timestamps at all, you can disable it once for all for that particular model using :
public $timestamps = false; inside your model. This will add additional step that whenever you want the timestamps to be updated, you need to assign them value manually like $object->created_at = Carbon::now()
Secondly, if you want those disabled for particular query, then as you mentioned in your question is one way.
Another way is using query builder. Now timestamps is the functionality associated with Eloquent. However, if you update using simple query builder, it does not update timestamps on its own.
So you can do :
DB::table('threads')
->where('id', $threadId)
->update([ 'votes' => DB::raw('votes + 1') ]);
However, I will personally prefer using Eloquent way of doing this if given a choice.
Update
You can now pass additional parameter to increment function to specify what other columns you would like to update.
So this will become :
$thread = Thread::find($threadId);
$thread->increment('votes', 1, [
'updated_at' => $thread->updated_at
]);
old thread but with laravel 7 and php7.4 you can do
Thread::where('id', $threadId)
->where(fn($q) => $q->getModel()->timestamps = false)
->increment('like_count');
older versions of php:
Thread::where('id', $threadId)
->where(function($q) {$q->getModel()->timestamps = false;})
->increment('like_count');
You could encapsulate the whole process into one method of the model.

Eloquent 5.4 - HasMany Object Mass Update Timestamps

really need your help here. ( I don't know what I want is possible on Eloquent )
Lets pretend this Relationship: One user can have many Childs
Note: Ignore problems in the code, this is just an example.
Now lets add some code into it.
// Return HasMany Object Instance from Eloquent.
$hasMany = $user->childs()
// Perform Mass Update.
$hasMany->update(['born_at' => Carbon::now])
So far nothing wrong with it, the first line returns an HasMany Object ( Documentation )
The problem is that Mass Updating touches my Model's timestamps ( created_at, updated_at ) and specially for this update I don't want it to do that.
Disabling it on the Model is not an option for me I do use the timestamp touch normally but I don't want to use in this case.
Neither I want to iterate over the Collection ( $user->childs ) because I have many rows to update and its an overhead to generate one query for each Model to update.
What I expect for an answer to this question: Simple, I just want an way to turn off the timestamps to do the mass updating or something like that.
( Normally on a single Model you can disable it like this: $model->timestamps = false, but this will not work here because hasMany instance does not have this attribute. )
You could set the property default of the model to false. So in you're model class you will have:
public $timestamps = false;
But this will always disable the timestamps until je use:
$model->timestamps = true;
In case someone finds this through Google:
One possible solution is to Fallback to the base QueryBuilder:
(new Child)
->newQuery()
->toBase()
->where('parent_id', $model->id)
->update([
'born_at' => Carbon::now,
]);
Of course, one could just use here something like DB::table(...)...

How to select specific columns in laravel eloquent

lets say I have 7 columns in table, and I want to select only two of them, something like this
SELECT `name`,`surname` FROM `table` WHERE `id` = '1';
In laravel eloquent model it may looks like this
Table::where('id', 1)->get();
but I guess this expression will select ALL columns where id equals 1, and I want only two columns(name, surname). how to select only two columns?
You can do it like this:
Table::select('name','surname')->where('id', 1)->get();
Table::where('id', 1)->get(['name','surname']);
You can also use find() like this:
ModelName::find($id, ['name', 'surname']);
The $id variable can be an array in case you need to retrieve multiple instances of the model.
By using all() method we can select particular columns from table like as shown below.
ModelName::all('column1', 'column2', 'column3');
Note: Laravel 5.4
You first need to create a Model, that represent that Table and then use the below Eloquent way to fetch the data of only 2 fields.
Model::where('id', 1)
->pluck('name', 'surname')
->all();
Also Model::all(['id'])->toArray() it will only fetch id as array.
Get value of one column:
Table_Name::find($id)->column_name;
you can use this method with where clause:
Table_Name::where('id',$id)->first()->column_name;
or use this method for bypass PhpStorm "Method where not found in App\Models":
Table_Name::query()->where('id','=',$id)->first()->column_name;
in query builder:
DB::table('table_names')->find($id)->column_name;
with where cluase:
DB::table('table_names')->where('id',$id)->first()->column_name;
or
DB::table('table_names')->where('id',$id)->first('column_name');
last method result is array
You can use get() as well as all()
ModelName::where('a', 1)->get(['column1','column2']);
From laravel 5.3 only using get() method you can get specific columns of your table:
YouModelName::get(['id', 'name']);
Or from laravel 5.4 you can also use all() method for getting the fields of your choice:
YourModelName::all('id', 'name');
with both of above method get() or all() you can also use where() but syntax is different for both:
Model::all()
YourModelName::all('id', 'name')->where('id',1);
Model::get()
YourModelName::where('id',1)->get(['id', 'name']);
To get the result of specific column from table,we have to specify the column name.
Use following code : -
$result = DB::Table('table_name')->select('column1','column2')->where('id',1)->get();
for example -
$result = DB::Table('Student')->select('subject','class')->where('id',1)->get();
use App\Table;
// ...
Table::where('id',1)->get('name','surname');
if no where
Table::all('name','surname');
If you want to get a single value from Database
Model::where('id', 1)->value('name');
Also you can use pluck.
Model::where('id',1)->pluck('column1', 'column2');
You can use Table::select ('name', 'surname')->where ('id', 1)->get ().
Keep in mind that when selecting for only certain fields, you will have to make another query if you end up accessing those other fields later in the request (that may be obvious, just wanted to include that caveat). Including the id field is usually a good idea so laravel knows how to write back any updates you do to the model instance.
You can get it like
`PostModel::where('post_status', 'publish')->get(['title', 'content', 'slug', 'image_url']`)
link
you can also used findOrFail() method here it's good to used
if the exception is not caught, a 404 HTTP response is automatically sent back to the user. It is not necessary to write explicit checks to return 404 responses when using these method not give a 500 error..
ModelName::findOrFail($id, ['firstName', 'lastName']);
While most common approach is to use Model::select,
it can cause rendering out all attributes defined with accessor methods within model classes. So if you define attribute in your model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the user's first name.
*
* #param string $value
* #return string
*/
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
}
And then use:
TableName::select('username')->where('id', 1)->get();
It will output collection with both first_name and username, rather than only username.
Better use pluck(), solo or optionally in combination with select - if you want specific columns.
TableName::select('username')->where('id', 1)->pluck('username');
or
TableName::where('id', 1)->pluck('username'); //that would return collection consisting of only username values
Also, optionally, use ->toArray() to convert collection object into array.
If you want to get single row and from the that row single column, one line code to get the value of the specific column is to use find() method alongside specifying of the column that you want to retrieve it.
Here is sample code:
ModelName::find($id_of_the_record, ['column_name'])->toArray()['column_name'];
If you need to get one column calling pluck directly on a model is the most performant way to retrieve a single column from all models in Laravel.
Calling get or all before pluck will read all models into memory before plucking the value.
Users::pluck('email');
->get() much like ->all() (and ->first() etc..) can take the fields you want to bring back as parameters;
->get/all(['column1','column2'])
Would bring back the collection but only with column1 and column2
You can use the below query:
Table('table')->select('name','surname')->where('id',1)->get();
If you wanted to get the value of a single column like 'name', you could also use the following:
Table::where('id', 1)->first(['name'])->name;
For getting multiple columns (returns collection) :
Model::select('name','surname')->where('id', 1)->get();
If you want to get columns as array use the below code:
Model::select('name','surname')->where('id', 1)->get()->toArray();
If you want to get a single column try this:
Model::where('id', 1)->first(['column_name'])->column_name;

Doctrine toarray does not convert relations

I followed doctrine documnetation to get started. Here is the documentation.
My code is
$User = Doctrine_Core::getTable("User")->find(1);
when I access relations by $User->Phonenumbers, it works. When I convert User object to array by using toArray() method, it does not convert relations to array. It simply display $User data.
Am I missing something?
By using the find method you've only retrieved the User data which is why the return of toArray is limited to that data. You need to specify the additional data to load, and the best place to do this is usually in the original query. From the example you linked to, add the select portion:
$q = Doctrine_Query::create()
->select('u.*, e.*, p.*') // Example only, select what you need, not *
->from('User u')
->leftJoin('u.Email e')
->leftJoin('u.Phonenumbers p')
->where('u.id = ?', 1);
Then when toArray'ing the results from that, you should see the associated email and phonenumber data as well.
I also noticed an anomaly with this where if you call the relationship first then call the ToArray, the relationship somehow gets included. what i mean is that, taking your own eg,
$User = Doctrine_Core::getTable("User")->find(1);
$num= $User->Phonenumbers->office; // assumed a field 'office' in your phone num table
$userArray = $user->toArray(true);
In the above case, $userArray somehow contains the whole relationship. if we remove the $num assignment it doesn't.
am guessing this is due to doctrine only fetching the one record first, and it's only when you try to access foreign key values that it fetches the other related tables

Resources