Efficient way to segregate data in php - php

I am using Laravel and I want to retrieve the data using Eloquent.
My controller:
public function getquesdet(){
$id = Request::input('id');
$question = Question::where('q_id','=',$id)->with('qtags.tags')->with('comments')->with('answers')
->first();
$i=0;
$tagnames[]=0;
foreach ($question['qtags'] as $value) {
$tagnames[$i] = $value['tags']['tag'];
$i++;
}
$j=0;
$comments[]=0;
foreach ($question['comments'] as $value) {
$comments[$j] = $value['comment'];
$j++;
}
$k=0;
$answers[]=0;
foreach ($question['answers'] as $value) {
$answers[$k] = $value['answer'];
$k++;
}
return array('question'=>$question['title'],'body'=>$question['body'],'tags'=>$tagnames,'comments'=>$comments,'answer'=>$answers);
}
As you can see, I feel that the usage of foreach loops is not efficient. Using for loops might take more time. All I want is to know that if there is any efficient workaround to this.
The $question is returned as :

I think that looks like an efficient solution. Three for loops after each other gives complexity O(3n) -> O(n) which is good and should be fast. If you have very large collections of comments, tagnames and questions I would try to solve the problem at SQL level which should be even faster, but for small resultsets this should be good enough.
Big O notation

If you want selected columns from the table then you can use select() method as:
$question = Question::where('q_id','=',$id)
->select('title', 'body')
->with([
'qtags' => function($q) {
$q->select('tag');
},
'qtags.tags' => function($q) {
$q->select('comment');
},
'answers' => function($q) {
$q->select('answer');
}
])
->first();
Then you can return it as an array:
return $question->toArray();
Docs

Related

Work with foreach in Laravel: delete same values

I found way to delete duplicate values in the result but it doesn't work correctly. How to fix it?
I have next code:
public function getListPositionByApplication($app_id){
// $app_id = \Request::input('app_id');
$list = SparePartApplicationPositionProvider::where('app_id',$app_id)->with(['provider','application_position'])->orderBy('apos_id')
->get();
$aa=0;
foreach ($list as $value) {
if($value->apos_id==$aa){
$value->application_position->name_detail='----';
}
$aa = $value->apos_id;
Log::info($value->apos_id);
}
return $list;
}
Log::info give next information: 26,26,26,26,26,26,27,27,27,27,27,27,28
but $value->application_position->name_detail have '----' in all cases with the exception of last value
#Hussein is right, group by the common column and don't do a foreach. Let the Eloquent DB do the heavy lifting. Notice the callback where you include your second table.
$list = SparePartApplicationPositionProvider::where('app_id',$app_id)
->with(['provider','application_position' => function ($query){
$query->groupBy('name_detail');
}])
->orderBy('apos_id')
->get();
You can try collection unique() method.
https://laravel.com/docs/master/collections#method-unique

Doctrine: use foreach in a find one

I want to use 99% of my code to do two queries.
When accessing api.com/jobs it will display all my jobs, but acessing api.com/jobs/65 will only display that entire job.
My improvement is to have an if before the foreach, to detect whether it is one single id or to list all.
My code looks like this:
$Repo = $entityManager->getRepository('App\Models\Entity\Jobs');
if ($param){
$jobs = $Repo->find($param);
} else{
$jobs = $Repo->findAll();
}
foreach ($jobs as $j_key=>$job){
...
}
The problem is: the findAll() is doing great but I can't enter the foreach with the find() only;
Is any way to do what I want?
If you must use this approach (I would prefer to separate concerns) put the result into an array;
edited
$Repo = $entityManager->getRepository('App\Models\Entity\Jobs');
if ($param){
$jobs[] = $Repo->find($param);
} else{
$jobs = $Repo->findAll();
}
foreach ($jobs as $j_key => $job){
// there is a risk that whats in $jobs might not be what you expect. So check.
if(!$job instanceOf Jobs::class) {
continue;
}
}
alternatively, use findBy as itll return a Collection object which will iterate.
$Repo->findBy(['column' => 'value']);
I would suggest having different pieces of code to deal with the two cases but, if you would rather keep them together:
$Repo = $entityManager->getRepository('App\Models\Entity\Jobs');
if ($param){
$jobs = array($Repo->find($param));
} else{
$jobs = $Repo->findAll();
}
foreach ($jobs as $j_key=>$job){
...
}
This will create an array with a single element in the case a request is made for a single job, and a request for multiple jobs will return all the matching jobs.

Laravel 5.5 Collection where like

i am filtering data using collections. But i need to use like method. I had tried to write like this : ('name', 'LIKE', '%value%') but it did not work.
Here is my method :
protected function filterData(Collection $collection, $transformer) {
foreach (request()->query() as $query => $value) {
$attribute = $transformer::originalAttribute($query);
if (isset($attribute, $value)) {
$collection = $collection->where($attribute, $value);
}
}
return $collection;
}
The 1st question is whether you really know what you are doing. If you take data from database and then filter it just to take some elements it's definitely not the best way because you can take from database for example 100000 records just to finally have only 2 elements and it will kill your application performance.
But assuming you really want to filter using Support collection, there is no where together with LIKE because you are just filtering an array. If you want to use something similar to like instead of:
$collection = $collection->where('name', $value);
you can use:
$collection = $collection->reject(function($element) use ($value) {
return mb_strpos($element->name, $value) === false;
});
depending on what you really have in collection instead of $element->name you might need to use $element['name']

Laravel - get the only item properties from collection without the foreach loop

I wonder is there a way to get an only item properties without a foreach loop. Since I have a query where in most of the cases there will be only one item in collection, and I need to change the status in the pivot table for only that case, I wonder is there some elegant way of doing this without the foreach loop. This is the case I am talking about:
$opponents = $quiz
->players()
->where('id', '!=', $player->id)
->get();
if ($opponents->count() < 2) {
$quiz->status = 'finished';
$quiz->save();
foreach ($opponents as $opponent) {
$quiz->players()->updateExistingPivot($opponent->id, ['status' => 'dropped']);
}
}
You can use the function first() like this:
$quiz->players()->updateExistingPivot($opponents->first()->id, ['status' => 'dropped']);

Laravel whereIn with a where clause on each array item

Say I have a user object (which belongsToMany groups) and I'm doing a whereIn with an array of their respected ids like so:
whereIn('user_id', $group->users->modelKeys())
I need to, however, set a condition where I only pull data from each array item based on a condition of the group_user pivot table, "created_at" (which is basically a timestamp of when that user was added to the group).
So I need something like this:
whereIn('user_id', $group->users->modelKeys())->whereRaw('visits.created_at > group_user.created_at')
That doesn't work though because it's not doing the whereRaw for each array item but it's doing it once for the query as a whole. I might need to do something like a nested whereIn but not sure if that'll solve it either. Thoughts?
My full query as it is now:
$ids = $group->users->modelKeys();
return DB::table('visits')->whereIn('user_id', function($query) use ($ids) {
$query->select('user_id')->from('group_user')->whereIn('group_user.user_id', $ids)->whereRaw('visits.created_at > group_user.created_at');
})->sum("views");
Ok got it to work using nested loops instead:
$visits = DB::table('visits')->whereIn('user_id', $group->users->modelKeys())->get();
$sum = 0;
foreach($group->users as $user) {
foreach($visits as $visit) {
if($visit->user_id == $user->id) {
if($visit->created_at >= $user->pivot->created_at) {
$sum += $visit->views;
}
}
}
}
return $sum;
Would still like to see if it's possible to do it in a single query, no array looping.
Solved it! The foreach loop approach was making calls take waaaay too long. Some queries had over 100k records returning (that's a lot to loop through) causing the server to hang up. The answer is in part a big help from Dharmesh Patel with his 3rd edit approach. The only thing I had to do differently was add a where clause for the group_id.
Here's the final query (returns that 100k results query in milliseconds)
//Eager loading. Has overhead for large queries
//$ids = $group->users->modelKeys();
//No eager loading. More efficient
$ids = DB::table('group_user')->where('group_id', $group->id)->lists('user_id');
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.user_id')->on('visits.created_at', '>=', 'group_user.created_at');
})->whereIn('group_user.user_id', $ids)->where('group_id', $group->id)->sum('views');
Have you considered using a foreach?
$users = whereIn('user_id', $group->users->modelKeys());
foreach ($users as $user) {
// do your comparison here
}
I guess you need to use JOINS for this query, following code may take you in right direction:
$ids = $group->users->modelKeys();
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.user_id')
->whereIn('group_user.user_id', $ids)
->whereRaw('visits.created_at > group_user.created_at');
})->sum("views");
EDIT
$ids = $group->users->modelKeys();
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.user_id');
})->whereIn('group_user.user_id', $ids)
->whereRaw('visits.created_at > group_user.created_at')->sum("views");
EDIT
$ids = $group->users->modelKeys();
return DB::table('visits')->join('group_user', function ($query) use ($ids) {
$query->on('visits.user_id', '=', 'group_user.id') // group_user.id from group_user.user_id as per the loop
->on('visits.created_at', '>=', 'group_user.created_at');
})->whereIn('group_user.user_id', $ids)
->sum("views");

Categories