I have a slow performance issue in comparing multiple arrays.
The algorithm that I've made is:
I'll be looping first the arrays by foreach
foreach($branches as $index => $branch) {
if (!$identical) {
continue;
}
// Basically this $permissions will be fetching data from the database.
$permissions = $this->get()
->where('user_id', $userId)
->where('branch_id', $userBranchId)
->sortBy('permission_id')
->pluck(['permission_id']);
// if index = 0 then I'll initialize the temporary variable (ARRAY) $initial otherwise I'll compare two variable (ARRAY)
if ($index === 0) {
$initial = $permissions;
} else {
if ($initial != $permissions) {
$identical = false;
}
}
}
So to compare multiple arrays, I use only this if condition and comparative symbol (==). But I'm thinking if
Don't query database in loop, you can use array_column to export branches array to columns "userId" and "userBranchId"
finaly build query using "whereIn"
I agree with #thaiha, you could reduce your queries to just one, and after make the comparison of the arrays. For example in Mysql you could do something like.
select * from `permissions` where (`user_id `, `branch_id `) in (('1', '2'), ('2', '4'));
then for comparing the arrays, you could do your loop, or use one of the php functions to compare arrays like array_diff is you are expecting all to match a specific set of permission. Or something like that.
Instead of looping array. I suggest that use eager loading instead.
Branch model
public function permissions(){
return $this->hasMany('App\Permission', 'foreign_key', 'local_key');
}
and in your controller
Branch::with('permissions')->where($condition)->get();
Read document here: https://laravel.com/docs/6.x/eloquent-relationships
Related
Is there a way to pass multiple where conditions into the Laravel's collection? I mean something like:
$filters = [['col1', '=', 'val1'], ['col2', '=', 'val2']];
$found = $collection->where($filters)->first();
I know it works with the eloquent query builder, but not quite with the collections. I know I can chain multiple ->where statements (even within the foreach loop) but I need to have the original collection object, and cloning it works, but isn't so fast.
$localCollectionObject = $localCollection->first(function ($value, $key) use ($remoteCollectionObject, $compareColumnNames) {
foreach ($compareColumnNames as $compareColumnName) {
if ($value->{$compareColumnName} != $remoteCollectionObject[$compareColumnName]) {
return false;
}
}
return true;
});
This works fine also, but is even slower than clone $localCollection.
Or maybe I can "reset" where statements somehow?
I'm searching it within foreach loop with different conditions and that's a problem.
You can always use your own closure with filter:
$collection->filter(function ($item) {
return $item->col1 == 'val1' && $item->col2 == 'val2';
});
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']
How can convert the result from eloquent to associative array. I need to select tow column and have one as key and another as value. Here is the closet I got, however the value is an array. I want it to be only "my_value" column.
$array = Post::select('my_key','my_value')->get()->keyBy('my_key')
You should use lists (Laravel 5.1) or pluck (Laravel 5.2+):
$array = Post::lists('my_value', 'my_key');
or
$array = Post::pluck('my_value', 'my_key');
I found a way to do so, I'm not sure whether it's proper way to do this performance wise though...
$array = Post::select('my_key','my_value')->get()->mapWithKeys(function ($item) {
return [$item['my_key'] => $item['my_value']];
})->toArray();
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");
I have the function below in my model which return an array of data. I want to make another operation on each row and add the result to array of data before to return it.
function get_all_annonce()
{
$this->db->select(" * from annonce");
$this->db->order_by("annonce.DATEDEBUTANNONCE","asc");
$q=$this->db->get();
if($q->num_rows()>0)
{
foreach($q->result() as $row)
{
$data[]=$row;
//I try to add another result
$experience=$this->get_experience($row->NUMUSER);
$data['experience']=$experience;
}
return $data;
}
}
But I have an error when I try to access to $experience or $row->'experience' in my view. How can I fix it ?
The $data variable is defined in the wrong scope. You defined it inside the foreach loop but you try to return it after. Try adding $data = Array(); above the foreach.
In addition to the answer above.
First you have unnecessary assignments, you can do it in one line.
2nd - when you use [] - it will create index automatically and the row will be added as an array to that index. You get a multidimensional array( 0 => result 1, 1 => result 2 etc).
If you want to add the 'experience' key to the result, you cannot add it directly to data.
You get an array that will have keys 0,1,2,3,4 ... 'experience' as last key - each time it is overwritten.
One way would be to use a variable for key (or use for loop instead):
$i = 0;
foreach($q->result() as $row)
{
$data[$i]=$row;
$data[$i]['experience'] = $this->get_experience($row->NUMUSER);
}
If you used only [] for both, it would assign different key for each one every iteration.