How to pass an array from a query inside a foreach - php

I have the following query that will bring all Auth user friends :
$usrusrmembs = DB::table('usrusrs')
->where('accepted', 1)
->where('user_id', $u_id)
->orwhere('friend_id', $u_id)
->pluck('friend_id', 'user_id');
Next is a foreach to loop the ids received from the above query, get the posts of all Ids, and then sending the result to a blade :
foreach($usrusrmembs as $key => $val){
$rec_users = ($key == $u_id) ? $val : $key;
$f_usrs_psts = DB::table('posts')
->where('posts.user_id', $rec_users)
->get();
}
return view('updates', ['f_posts' => $f_usrs_psts] );
The output in the blade shows only posts of one friend , while ignores the others. I feel there is a problem in the posts query, where it only sends to the blade the last processed ID. If this is the problem, then how can I solve it ?

This is where Laravel really shines. Take advantage of the relationships and do this all in one query with eager loading. I don't know what your model relations are, but something like this:
$usrusrmembs = \App\UserUserModel::where('accepted', 1)
->where('user_id', $u_id)
->orwhere('friend_id', $u_id)
->with('posts')
->get();
If you want more control, you can use a combination of closures and whereHas, but this above should get you close. Then, in your view you can loop on the posts for each:
#foreach($usrusrmembs as $usr)
echo $usr-name // etc
#foreach ($usr->posts as $post)
echo $post->whatever
#endforeach
#endforeach
This is not going to give you exactly what you need, but the idea should help you to work through it and you can skip the whole n+1 issue by removing the foreach loop in your controller.

Related

Laravel Chunk to show thousands records of data?

I want to show almost a hundred records from the database. But the process very slow. So I try to add a chunk in the blade
Here is my controller
$user= User::where('status', '>=', '2')->get();
Here is my view.blade.php
#foreach($user->chunk(100) as $chunk)
#foreach ($chunk as $data)
<tr>
<td>{{$data->name}}</td>
<td>xxxx<td>
<tr>
#endforeach
#endforeach
But I didn't find the difference between before and after-use chunks. I know I can use data tables serverside/vajra/larawire. I also try with paginating but the searching/sort function not working coz I use data tables.
Since this website already lives. is there any short-term solution for that? because I have planning to implementation the serverside for the permanent fix but can do it asap.
if I chunk like this in the controller
$user= User::select('status','date','id','name','xxx')->where('status', '>=', '2')->orderBy('date_assign_fa','DESC')
->chunk(50, function($user) {
foreach ($users $user) {
**what i put in here if in im blade i also have foreach?**
}
});
As you pointed out that you are using relationship in this case:
You should be using eager loading:
$user = User::with('Detail')->where('status', '>=', '2')->get();
https://laravel.com/docs/8.x/eloquent-relationships#eager-loading
Also you can do something like this to receive speficic columns from relationship:
$user = User::with([
'Detail' => function ($query) {
$query->select('column1', 'column2');
}
])->where('status', '>=', '2')->get();
This could be single solution to all the performance issues
Old answer
As arrays are less heavy then collections one thing you could try is:
$usersArray = [];
User::select('status','date','id','name','xxx')
->where('status', '>=', '2')->orderBy('date_assign_fa','DESC')
->chunk(100, function($users) use ($usersArray) {
foreach ($users as $user) {
$usersArray[] = $user;
}
});
And then loop $usersArray in your view.
Also it could be simpler / faster to just use raw query:
$users = DB::select(
"SELECT
status, date, id, name, xxx
FROM users
WHERE status >= 2
ORDER BY date_assign_fa DESC"
);
As this will return you array not collection and this should use less memory / processor

Laravel 5: How to use 'where' or 'orderBy' using eloquent?

First things first, this is not a duplicate of Laravel 4: how to "order by" using Eloquent ORM
I have this model User, then I have this other model Idea. I'm writting a view with the details of the user, and I want to post all the ideas the user has suggested. Now, the ideas have a status, the first status is "DRAFT", that means only the user who posted the idea can review it, the idea should not be shown to other users looking at the profile.
Using eloquent I could do something like:
#foreach($user->idea as $idea)
...display details...
#endforeach
My problem is that this approach would cycle through all ideas AND in the order where they were introduced. I can add an #if to show only what I want, like
#foreach($user->idea as $idea)
#if($idea->status !='DRAFT')
...show the idea...
#endif
#endforeach
But I don't think it's the smart way to do it, I'd like to do something like
$ideas = $user->idea->where('status', '<>', 'DRAFT')
and then cycle through the $ideas variable, or at least, I'd like somtehing like
$ideas = $user->idea->orderBy('created_at', 'desc')
But I have no idea on how to do it.
You can do it like this:
$ideas = $user->idea()
->where('status', '<>', 'DRAFT')
->orderBy('created_at', 'desc')
->get();
When you use ->idea() it will start a query.
For more information about how to query a relationship: https://laravel.com/docs/5.7/eloquent-relationships#querying-relations
You can user where clause like this
DB::table('products_to_categories')
->where('categories_id', $id )
->update([
'categories_id' => $unassigned_cat_id
]);
and you can use orderBy clause like this
DB::table('products_to_categories')
->where('categories_id', $id )
->orderBy('subId','DESC');

Laravel Pass Comments with Posts

I have a One to Many relationship set up for my "announcements" having many "comments".
Currently, when my user loads up the app page, I have it send the 30 most recent announcements like such:
Route::get('/app', function () {
$posts = Announcement::take(30)->orderBy('id', 'desc')->get();
return View::make('app')->with([
//posts
'posts' => $posts,
//orders
'orders' => $orders
]);
}
When I echo out the announcements in blade using a foreach loop through the $posts object, I want to also echo out the comments for each post under the respective post.
Is it possible to pass the comments for a post as part of the actual post object? For example, it would be nice if I could do this:
#foreach ($posts as $post)
//echo out the post
{{$post->content}}
//echo out the comments relating to this post
{{$post->comments}}
#endforeach
#Amr Aly gave you the right answer, and I would like to add on top of it.
When you loop through your comments as he showed you (and you should), it will make a different query for each comment. SO if you have 50 comments, that's 50 more queries.
You can mitigate that by using eager loading
$posts = Announcement::with('comments')
->take(30)->orderBy('id', 'desc')
->get();
Then just loop the way he showed you. THis will limit the queries to 2 only. You can read more from the docs here: https://laravel.com/docs/5.4/eloquent-relationships#eager-loading
You can add another foreach for your comment like this:
#foreach ($posts as $post)
//echo out the post
#if($post->comments->count())
#foreach ($post->comments as $comment)
// {{ $comment }}
#endforeach
#endif
#endforeach

Laravel: how to access record by id in view

In my controller I return a view with a ?collection $programs? from an eloquent query to the view.
Controller
$programs = ScheduledProgram::where('registration_start_date', '<=', $today)
return View::make('admin/register_users/show', compact(programs));
I wan to do something like this without it running a new query from the view...
VIEW
{{$program->find(id)}}
I know that $programs is a dataset that already has the record, but I don't know the way to access the element by ID this way.
How do I do this?
(sorry, seems like an obviously searchable question but my search terms aren't comming up with the answer)
in that case you need to make a #foreach in $programs to access the data. Like this:
#foreach($programs as $key => $value)
{{$value->id}}
#endforeach
If the return is only one line you can do this:
{{$programs[0]->id}}
1.You have to add the method get (converts the "dataset " in a Laravel collection) to iterate the collection.
$programs = ScheduledProgram::where('registration_start_date', '<=', $today)
->get()
2.If you want to get a single record:
ScheduledProgram::where('registration_start_date', '<=', $today)->where('id', 5)->get();

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