Laravel replicate multiple rows didn't work - php

I want to replicate multiple relational rows to the same table with the diff job id.
But it doesn't work.
Here is my code
$parentJobUnits = Unit::where('job_id',$jobId)->get(); //may be single or multiple rows for units.
$JobCopy = $job->replicate()->save(); //main job copied for new units.
$partsCopy = array_map(function(Unit $unit) use($JobCopy)
{
$newUnit = $unit->replicate();
$newUnit->job_id = $JobCopy->id;
return $newUnit->save();
}, $parentJobUnits);
The above code is not working for multiple row replication.

Try removing the save method which causes a true return and not the copied record
correct: $job->replicate();
incorrect: $job->replicate()->save();
otherwise I'd do something like...
$parentJobUnits = Unit::where('job_id',$jobId)->get();
foreach($parentJobUnits as $unit)
{
$unit->replicate()->save();
}

$CollageData= Collage::where('student_id',$student_id)->get();
foreach($CollageData as $key => $collage )
{
$collage->replicate()->save();
}
This Works for me.

replicate() function does not work on get() function. It only works with find() function.
$parentJobUnits = Unit::where('job_id',$jobId)->get();
foreach($parentJobUnits as $key => $value)
{
$new_unit = Unit::find($value->id)->replicate(); // $value->id is your primary key
$new_unit->save();
}
It will replicate the multiple rows.

Related

Improve performance of multiple row insertions in Laravel

I have a controller API method where I insert many rows (around 4000 - 8000), before inserting a new row I also check if a venue with the same ame was added already in the zone sothat's another Elouent call, my issue is I usually get timeout errors becuase the row inserting takes too much, I use set_time_limit(0) but this seems too hacky.
I think the key is the validation check I do before inserting a new row.
//Check if there is a venue with same name and in the same zone already added
$alreadyAdded = Venue::where('name', $venue['name'])->whereHas('address', function ($query) use ($address){
$query->where('provinceOrState' , $address['provinceOrState']);
})->orWhere('venueId',$venue['venueId'])->first();
Is there a way I can improve the performance of this method ? This is my complete method call:
public function uploadIntoDatabase(Request $request)
{
set_time_limit(0);
$count = 0;
foreach($request->input('venuesToUpload') as $index => $venue)
{
//Check if there is a venue with same name and in the same zone already added
$alreadyAdded = Venue::where('name', $venue['name'])->whereHas('address', function ($query) use ($address){
$query->where('provinceOrState' , $address['provinceOrState']);
})->orWhere('venueId',$venue['venueId'])->first();
if(!$alreadyAdded)
{
$newVenue = new Venue();
$newVenue->name = $venue['name'];
$newVenue->save();
$count++;
}
}
return response()->json([
'message' => $count.' new venues uploaded to database',
]);
}
use only one request to add the venues
$newVenues = [];
$count = 0;
foreach($request->input('venuesToUpload') as $index => $venue) {
//Check if there is a venue with same name and in the same zone already added
$alreadyAdded = Venue::where('name', $venue['name'])->whereHas('address', function ($query) use ($address){
$query->where('provinceOrState' , $address['provinceOrState']);
})->orWhere('venueId',$venue['venueId'])->count();
if(!$alreadyAdded) {
$newVenues [] = ['name' => $venur['name'];
}
}
if ($newVenues) {
$count = count($newVenues);
Venue::insert($newVenues);
}
As for the verification part, change the first to count cause you dont need to recover the data, just the information that it exists. And since you're verifying with both name and id, you can do some custom query that verifies all values in one query using a static table made from the request inputs and joining on the existing venues table where venues.id = null.

How do I remove duplicate rows with same column values in Laravel?

I'm trying to make a artisan command in Laravel to remove all venues that have the same address and leave the one with the lowest ID number (so first created).
For this I need to check 3 fields: 'street', 'house_number', 'house_number_addition'
This is how far I've got:
$venues = Venue::select('street', 'house_number', 'house_number_addition', DB::raw('COUNT(*) as count'))
->groupBy('street', 'house_number', 'house_number_addition')
->having('count', '>', 1)
->get();
foreach ($venues as $venue) {
$this->comment("Removing venue: {$venue->street} {$venue->house_number} {$venue->house_number_addition}");
$venue->delete();
}
Only the delete is not working but is also not giving an error.
To be able to delete an item, Eloquent needs to know it's id. If you make sure your models' id is queried, you can call delete() without issues.
In your query, however, that won't work because you have a GROUP_BY statement, so SQL doesn't allow you to select the id column (see here).
The easiest solution here is to utilize Eloquent's Collection class to map over the models, something like:
$uniqueAddresses = [];
Venue::all()
->filter(function(Venue $venue) use (&$uniqueAddresses) {
$address = sprintf("%s.%s.%s",
$venue->street,
$venue->house_number,
$venue->house_number_addition);
if (in_array($address, $uniqueAddresses)) {
// address is a duplicate
return $venue;
}
$uniqueAddresses[] = $address;
})->map(function(Venue $venue) {
$venue->delete();
});
Or, to make your delete query a little more efficient (depending on how big your dataset is):
$uniqueAddresses = [];
$duplicates = [];
Venue::all()
->map(function(Venue $venue) use (&$uniqueAddresses, &$duplicates) {
$address = sprintf("%s.%s.%s",
$venue->street,
$venue->house_number,
$venue->house_number_addition);
if (in_array($address, $uniqueAddresses)) {
// address is a duplicate
$duplicates[] = $venue->id;
} else {
$uniqueAddresses[] = $address;
}
});
DB::table('venues')->whereIn('id', $duplicates)->delete();
Note: the last one will permanently delete your models; it doesn't work with Eloquent's SoftDeletes functionality.
You could, of course, also write a raw query to do all this.

How to delete multiple row from DB table by laravel?

I want to delete all comments from comments table.I did that by doing this but this is not the laravel way. Anyone answer the correct steps please .
$comments = Comment::where('post_id',$id)->get();
if(count($comments)>1)
{
$comment_id= [];
foreach ($comments as $i)
{
$comment_id[] = $i->id;
}
for($i=0;$i<count($comments);$i++)
{
Comment::find($comment_id[$i])->delete();
}
}
elseif (count($comments)==1)
{
$comments->delete();
}
Since each Eloquent model serves as a query builder, try this, all in one line:
Comment::where('post_id',$id)->delete();
Tested in tinker, works as expected, returns count of deleted rows.
Documentation: https://laravel.com/docs/5.3/queries#deletes
You can try the following approach:
public function deleteAll(Request $request)
{
$ids = $request->ids;
Comment::whereIn('id',explode(",",$ids))->delete();
}
DB::table('users')->whereIn('id', $ids_to_delete)->delete();
or
$org-products()->whereIn('id', $ids)->delete();
Collect only ids from your command like below and destroy by model.
$yourRaws = YourModel::where('name', 'Enver')->get();
...
YourModel::destroy($yourRaws->pluck('id')->toArray());
Enjoy Your Coding !

Laravel - efficient way to check database parameters before insert

I have populate a form of which every text field generated is based on the database result. I simply name every text field using the id. Now when the form is filled, I use controller to save it. But prior to insert the database, I loop the Request::input() to check every item whether such entry is exist or not. I just wonder if there is efficient way to check every item in the loop to insert it into db. Here is my code
public function store(Request $request, $id, $inid)
{
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
$instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])
->where('iv_inid', '=', $inid)
->get();
foreach ($request->input() as $k => $v) {
$read = new InstrumentReading;
$read->iv_inid = $inid;
$read->iv_ipid = $k;
$read->iv_usid = Auth::user()->id;
$read->iv_reading = $v;
$read->save();
}
if ($instruments->count() > 0) {
//to filter the iv_ipid...
foreach($instruments as $instrument)
{
$instrument->iv_status = "VOID";
$instrument->save();
}
}
}
In words of efficent approach what you can do is to simple check / fetch ONLY all posible rows from the database, and the check in the loop if the row was already inserted. Also fetch only iv_ipid column, as we do not need all columns from the table to do our check. It will be faster to select only the column we need. You can use directly Fluent (Query Builder) over Eloquent to pull the data from database as it greatly increase the performance for a simple query like this.
public function store(Request $request, $id, $inid)
{
// Search only records with submitted iv_ipid, iv_inid and created today
$alreadyInserted = DB::table('instrument_readings')
->whereBetween('created_at', [
Carbon::now()->startOfDay(),
Carbon::now()->endOfDay()
])
// Get only records with submitted iv_ipid
->whereIn('iv_ipid', array_keys($request->input()))
// Get records with given iv_inid only
->where('iv_inid', $inid)
// For our check we need only one column,
// no need to select all of them, it will be fast
->select('iv_ipid')
// Get the records from DB
->lists('iv_ipid');
foreach ($request->input() as $k => $v) {
// Very simple check if iv_ipid is not in the array
// it does not exists in the database
if (!in_array($k, $alreadyInserted)) {
$read = new InstrumentReading;
$read->iv_inid = $inid;
$read->iv_ipid = $k;
$read->iv_usid = Auth::user()->id;
$read->iv_reading = $v;
$read->save();
} else {
//todo
}
}
This is the most efficent way suggested until now, because you fetch at once only the records you are interested in, not all records from today. Also you fetch only one column, the one that we need for out check. Eloquent ususlally give a lot of overheat on the perfomance, so in the suggested code I use directly Fluent, which will boost the speed this part of code is executed by ~ 20%.
Your mistake in the original code is that you are doing database call each time in a loop. When you need such a simple task as a check, never put database calls, queries etc. in a loop. It is an overkill. Instead select all needed data before the loop and then do your checks.
Now this is in case you only need to save new records to database. In case you want to manipulate each record in the loop, let's say you need to loop through each submited entry, get get the model or create it if it does not exists and then do something else with this model, the most efficent way then will be this one:
public function store(Request $request, $id, $inid)
{
foreach ($request->input() as $k => $v) {
// Here you search for match with given attributes
// If object in DB with this attributes exists
// It will be returned, otherwise new one will be constructed
// But yet not saved in DB
$model = InstrumentReading::firstOrNew([
'iv_inid' => $inid,
'iv_ipid' => $k,
'iv_usid' => Auth::user()->id
]);
// Check if it is existing DB row or a new instance
if (!$model->exists()) {
// If it is a new one set $v and save
$model->iv_reading = $v;
$model->save();
}
// Do something with the model here
.....
}
This way Laravel will check if model with the passed parameters already exist in database, and if so it will return it for you. If it does not exist, it will create new instance of it, so you can then set the $v and save to db. So you are good to go to do anything else with this model and you can be sure it exists in database after this point.
First approach (efficiency first)
Consider using a simple SQL INSERT IGNORE Query and use Fluent, i.e.:
Make a composite unique key containing:
iv_inid
iv_ipid
created_time, up to an hour granularity, this is important, because created_at might have a far greater granularity than your intended purpose, and might slow things down a bit.
Use DB, i.e.:
DB::query(
"INSERT IGNORE INTO $yourTable VALUES ( ... )"
);
Pros:
- Extremely fast, all the necessary checking is done on the DB Server
Cons:
- You cannot know which values triggered a duplicate value / unique key violation, as related errors are treated as warnings.
Second approach (convenience first)
Use firstOrFail, i.e.:
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// ... for
try {
InstrumentReading::where('iv_inid', $inid)
->where('iv_ipid', $k)
->whereBetween('created_at', [$startOfDay, $endOfDay])
->firstOrFail();
continue;
} catch (ModelNotFoundException $e) {
$instrumentReading = InstrumentReading::create([
// your values
]);
}
// ... endfor
Pros:
- Easy to implement
Cons:
- Somewhat slower than simple queries
Your code will send request to database every time you need to check the value. Instead, search all value of this day then check the value. This approach will send request to database only one time.
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// Search only this day
$instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])->get();
foreach($instruments as $instrument)
{
// Check the value
}

Replicate using foreign key - Laravel

I am trying to replicate certain data in my DB and I followed the steps as in the following link. Laravel 4: replicate to table
However, I need to replicate some other data using only a foreign key.I tried to use the find() method to get my data but returned nothing.The where clause returns my data but in the form of array which isn't accepted by the replicate method.
Anhy idea what i am doing wrong and how can I replicate my other data?!
Code:
$item = Cv::find($cv_id);
// return $item;
$clone = $item->replicate();
unset($clone['created_at'],$clone['updated_at']);
$data = json_decode($clone, true);
Cv::create($data);
//Skills
// return $cv_id;
$skills = Skill::where('cv_id', $cv_id);
$cloneSkills = $skills->replicate();
unset($cloneSkills['created_at'],$cloneSkills['updated_at']);
$skillData = json_decode($cloneSkills,true);
Skill::create($skillData);
For replicating skills you should probably use:
$skills = Skill::where('cv_id', $cv_id)->get();
foreach ($skills as $skill) {
$cloneSkill = $skill->replicate();
unset($cloneSkill['created_at'], $cloneSkill['updated_at']);
$skillData = json_decode($cloneSkill, true);
Skill::create($skillData);
}
You need to use get() to get all data and because $skills is Collection you need to use loop to replicate each skill.

Categories