CakePHP's "saveAll" need over 3 minutes to save? - php

I am making an application using CakePHP. I made an action which uses saveAll function.
And I thought it works well, because it doesn't need so much data, but it took over 3 minutes to save using saveAll, or other save function.
Does anyone find my mistakes?
phpMyadmin's columns:
id, rank, school_detail_id, total_score, school_name,
(there is about 300~400 data)
public function rank_update(){
$check_scores = $this->ClubScore->find('all', array('fields'=>array('id','total_score')));
$check_scores2 = Set::sort($check_scores, "{n}.ClubScore.total_score","DESC");
$rank_id=0;
$temp_score=0;
$temp = null;
$for_count=0;
foreach ($check_scores2 as $check_score):
if($temp_score != $check_score['ClubScore']['total_score']){
$rank_id++;
$temp_score = $check_score['ClubScore']['total_score'];
// make ranking by score. same score is same ranking.
}
$this->ClubScore->id = $check_score['ClubScore']['id'];
$this->ClubScore->saveField('rank', $rank_id);
endforeach;
}

Divide the query from foreach to simpler approach
get distinct total_score in desc order
$data = $this->ClubScore->find('all', array('fields'=>array('DISTINCT total_score'), 'order' => 'total_score DESC'));
and then simply save the key as rank for each total_score using updateAll and foreach

Thank you very much Abhishek and AgRizzo ,Nunser!!
Now, I've completely solved this problem. It takes only 1 or 2 seconds!!!!!
Here is the source code.
public function rank_update(){
$data = $this->ClubScore->find('all', array('fields'=>array('DISTINCT total_score'), 'order' => 'total_score DESC'));
$check_scores = $this->ClubScore->find('all', array('fields'=>array('id','total_score')));
$check_scores2 = Set::sort($check_scores, "{n}.ClubScore.total_score","DESC");
$ii = 0;
$temp = 0;
foreach($check_scores2 as $scores):
if($data[$ii]['ClubScore']['total_score']
== $scores['ClubScore']['total_score']){
$temp=$ii+1;
}else{
$ii++;
$temp=$ii+1;
}
$update_arr[] = array(
'ClubScore' => array(
'id' => $scores['ClubScore']['id'],
'rank' =>$temp,
)
);
endforeach;
$update_arr = Set::sort($update_arr, "{n}.ClubScore.id","ASC");
var_dump($update_arr);
foreach($update_arr as $update_arrs):
$this->ClubScore->updateAll(
array(
'ClubScore.rank' => $update_arrs['ClubScore']['rank'],
),
array(
'ClubScore.id' => $update_arrs['ClubScore']['id'],
)
);
endforeach;
}
Thank you very much.
Best regards.

Related

Recursive Function Result Not Ordered Correctly PHP

Good Day,
I am trying to get a correct ordered value from image above, I expect the value would be like below
2,3,4,5,6,7,8,19,9,20,10,11,12,13...18 (the order is from left top to right bottom)
I use this function below
function getUserEmptyLeg($sponsor_id){
$data_params = array(
":placement_id" => $sponsor_id,
":status" => "aktif"
);
$data = fetchAll("SELECT * FROM users WHERE placement_id =:placement_id AND STATUS=:status", $data_params);
$new_sponsor_ids = array();
$member = array();
foreach($data as $row){
$member[$row['user_id']] = array(
"placement_id" => $row['placement_id'],
"user_id" => $row['user_id'],
"member_name" => $row['username'],
"leg" => $row['leg']
);
$new_sponsor_ids[] = $row['user_id'];
}
foreach($new_sponsor_ids as $new_sponsor_id){
$member = array_merge($member, getUserEmptyLeg($new_sponsor_id));
}
return $member;
}
result from above function
2,3,4,5,8,9,10,11,12,13,14,...18,6,7,19,20
but the result from above function didn't output the result as i expected.
i am trying as best as possible to make my question clear, im sorry if you all confused when answering it. but i need help from you all, thanks.

Php Sphinx, Using Or in setFilter()

Below is my fetch code for sphinx. We have a filter named typeId which we use to compare bureau type. Now I have added one more filter named as altTypeId. Now I have to compare typeId and altTypeId both for same value such typeId = 6 OR altTypeId = 6.
I have tried several solutions but no one is working. I followed the solution on this link: https://sphx.org/forum/view.html?id=13474 but its not working as well.
protected function fetch() {
$this->_searchCriteria->orders = $this->sort;
$this->pagination->validateCurrentPage = false;
$this->_searchCriteria->paginator = $this->pagination;
$this->_searchCriteria->query.=" profileTypePlaceholderTexxxxxt";
Yii::app()->search->SetLimits($this->pagination->offset, $this->pagination->limit);
Yii::app()->search->SetFieldWeights($this->_fieldWeights);
if ($this->_searchCriteria->query)
Yii::app()->search->setMatchMode(SPH_MATCH_ALL);
else
Yii::app()->search->setMatchMode(SPH_MATCH_FULLSCAN);
Yii::app()->search->setFieldWeights(array(
'primary_comp' => 100,
'premium' => 80,
'standard' => 60,
'free' => 40,
'comp' => 20,
'title' => 5,
'description' => 5,
));
//Yii::app()->search->SetSortMode(SPH_SORT_EXTENDED, '#weight DESC');
Yii::app()->search->SetSortMode(SPH_SORT_EXTENDED,'#weight DESC, random DESC');
foreach ($this->_searchCriteria->filters as $filter) {
if ($filter[0] == 'range')
Yii::app()->search->SetFilterFloatRange($filter[1], (float) $filter[2] + 0.01, (float) $filter[3]);
else
Yii::app()->search->SetFilter($filter[0], array($filter[1]));
}
$results = Yii::app()->search->Query($this->_searchCriteria->query, $this->_searchCriteria->from);
$this->_data = array();
$this->_keys = array();
$this->_itemCount = $results['total'];
$this->pagination->itemCount = $this->_itemCount;
if ($this->_itemCount) {
$this->_populateItems($results['matches']);
}
}
Frankly putting both values in the same sphinx attrbute, seems easiest. MVA is perfect for this!
Couple of ways could be done, but just...
sql_query = SELECT id,title, CONCAT_WS(',',typeId,altTypeId) AS typeids FROM ...
sql_attr_multi = uint typeids from field
then just
->SetFilter('typeids', array(6));
finds results from EITHER column.
Otherwise if really want to only do it at query time, its something like
if ($filter[0] == 'typeid') {
Yii::app()->search->SetSelect("*, (typeid = {$filter[1]} OR altTypeId = {$filter[1]}) AS myfilter");
Yii::app()->search->SetFilter('myfilter', array(1));
} else ...

Laravel Bulk Update for multiple record ids

I want to mass update my records in Laravel but the records not getting updated. I have a different record for each Id. Below is what I am trying.
$ids = [5,6,8,9],
$updated_array = [
['name' => 'tarun'],
['name' => 'Akash'],
['name' => 'Soniya'],
['name' => 'Shalu'],
];
Model::whereIn('id', $ids)->update($updated_array);
Mass updates are used when you're trying to update multiple rows to the same values. You cannot mass update with different values.
Therefore, this would work, but would update all matching records to name of 'tarun':
Model::whereIn('id', $ids)->update(['name' => 'tarun']);
For your example, you could do:
foreach($ids as $key => $id) {
Model::where('id', $id)->update($updated_array[$key]);
}
But as far as I know, there's no way to do this without running 4 queries in Laravel and writing a raw SQL statement to accomplish this would even be messy.
You can use Laravel Upsert for mass update.
For example :
User::query()->upsert([
['id' => 1, 'email' => 'dadan#example.com'],
['id' => 2, 'email' => 'satria#example.com'],
], 'email');
This feature available in Laravel 8 or newer
Some good solutions for this issue are on the following post: https://github.com/laravel/ideas/issues/575
1) Can create custom function to do this, which uses raw SQL, as per barryvdh comment in the post.
public static function updateValues(array $values)
{
$table = MyModel::getModel()->getTable();
$cases = [];
$ids = [];
$params = [];
foreach ($values as $id => $value) {
$id = (int) $id;
$cases[] = "WHEN {$id} then ?";
$params[] = $value;
$ids[] = $id;
}
$ids = implode(',', $ids);
$cases = implode(' ', $cases);
$params[] = Carbon::now();
return \DB::update("UPDATE `{$table}` SET `value` = CASE `id` {$cases} END, `updated_at` = ? WHERE `id` in ({$ids})", $params);
}
This apparently increase performance by 13x
2) As per another comment, another idea is to do it on a per record basis, but do the whole thing as a single transaction, which makes it much faster.
DB::beginTransaction();
// your loop and updates;
if( !$user )
{
rollbackTransaction();
} else {
// Else commit the queries
commitTransaction();
}
3) There is a laravel library also that appears to try and solve this issue. https://github.com/mavinoo/laravelBatch
Note: I have not tried or tested any of the above solutions.
I think the only way to do this without n queries would be to
(optional) backup table
grab the records and create an array with all the updated data
bulk delete the records
bulk insert from array
That's 3 queries.
Iterating through n records is not practical for my application either; I was hoping there was an alternative but it looks like I'm going to have to implement this.
We can Insert and update batch (bulk) in laravel using this Package
Laravel Batch
Please try the below code:
$data_to_be_updated = [ ['id'=>5,'name' => 'tarun'], ['id'=>6, 'name' => 'Akash'],
['id'=>8, 'name' => 'Soniya'], ['id'=>9,'name' => 'Shalu']
];
foreach ($data_to_be_updated as $key => $value) {
$data = Model::where('id',$value['id'])->first();
if ($data) {
$data->name = $value['name'];
$data->save();
}
}
you can do it like this above mentioned:-
foreach($ids as $key => $id) {
Model::where('id', $id)->update($updated_array[$key]);
}

How to make these code shorter?

$categories = PostCategoryQuery::create()->find();
$categories_array = [];
foreach ($categories as $k => $category):
$posts_count = PostQuery::create()->filterByPostCategory($category)->count();
$categories_array[$k]['category'] = $category->getName();
$categories_array[$k]['count'] = $posts_count;
endforeach;
uasort($categories_array, "sortByCount");
function sortByCount($a, $b) {
return $a['count'] > $b['count'] ? -1 : 1;
}
I want to get 'categories' array order by numbers of its related posts, I think there must be a way to make my code shorter, hoping get some suggestions, thanks~
If in your ORM you could use a simple query with group by you simply could perform a select like
select category, count(*)
from post_category
inner join post on post_category.category_id = post.category_id
group by category
order by count(*)
and then your code will be reduced to the query..
I don't know your model exactly, but I would recommend something like that:
$categories_array = PostQuery::create('pq')
->joinPostCategory('pc')
->withColumn('COUNT(pq.id)', 'count')
->groupBy('pc.id')
->select(array('pc.name', 'count'))
->orderBy('count')
->find();
// Should return PropelArrayCollection(
// array('pc.name' => 'category1', 'count' => 123),
// array('pc.name' => 'category2', 'count' => 234)
// )
This way you avoid object hydrating as you do only select the columns that you need.

Cakephp Minimum value query

I have this issue with Cakephp where I can retrieve min value from score field but I cannot seem to get id for the same row using group aswell. Below is my code. What am I doing wrong?
function index() {
$this->Leaderboard->recursive = 0;
$this->set('leaderboards', $this->paginate());
$min = $this->Leaderboard->find('all',
array(
'fields' => array('MIN(Leaderboard.score) as min_score')
));
$minimum = $min[0][0]['min_score'];
pr($min);
echo $minimum;
if (!empty($this->data)) {
$this->Leaderboard->create();
if($this->data['Leaderboard']['score'] > $minimum){
if ($this->Leaderboard->save($this->data)) {
$this->Session->setFlash(__('The leaderboard has been saved', true));
$this->Leaderboard->delete($min[0]['Leaderboard']['id']);
} else {
$this->Session->setFlash(__('The leaderboard could not be saved. Please, try again.', true));
}
} else {
$this->Session->setFlash(__('Score not high enough for leaderboard',true));
}
}
$users = $this->Leaderboard->User->find('list');
$trips = $this->Leaderboard->Trip->find('list');
$this->set(compact('users', 'trips'));
}
U have to use sub-query for getting id for the same row as that of minimum one ..
Query would something like this
SELECT id
FROm Leaderboard
WHERE score IN (
SELECT MIN(score)
FROM Leaderboard
);
and how to use sub-queries in cakephp take a look at http://book.cakephp.org/view/1030/Complex-Find-Conditions
I have done the same task but in other way
$minimum = $this->Leaderboard->find('first',array(
'fields'=>array('MIN(score) as MinimumScore')));
$data = $this->Leaderboard->find('first',array(
'fields'=>array('id'),
'conditions'=>array('score'=>$minimum[0]['MinimumScore'])));
and now u can access id as
$id = $data['Leaderboard']['id'];
Hope this help
You can try something like this. ** Disclaimer -- I didn't test this code, but it's too long to fit in a comment, so...
$min = $this->Leaderboard->find('first', array(
'fields' => array('Leaderboard.id', 'Leaderboard.score'),
'order' => 'Leaderboard.score ASC'
)
);

Categories