Optimizing query with large result set - php

I have a CakePHP model, let's call it Thing which has an associated model called ItemView. ItemView represents one page view of the Thing item. I want to display how many times Thing has been viewed, so I do the following in my view:
<?php echo count($thing['ItemView']); ?>
This works, however as time goes on the result set of this query is going to get huge, as it's currently being returned like so:
array(
'Thing' => array(
'id' => '1',
'thing' => 'something'
),
'ItemView' => array(
(int) 0 => array(
'id' => '1',
'thing_id' => 1,
'created' => '2013-09-21 19:25:39',
'ip_address' => '127.0.0.1'
),
(int) 1 => array(
'id' => '1',
'thing_id' => 1,
'created' => '2013-09-21 19:25:41',
'ip_address' => '127.0.0.1'
),
// etc...
)
)
How can I adapt the model find() to retrieve something like so:
array(
'Thing' => array(
'id' => '1',
'thing' => 'something',
'views' => 2
)
)
without loading the entire ItemView relation into memory?
Thanks!

So it's pretty straight forward, we can make use of countercache - Cake does the counting for you whenever a record is added into/deleted fromItemView:
Nothing to change in your Thing.php model
Add a new INT column views in your things table.
In your ItemView.php model, add counterCache like this:
public $belongsTo = array(
'Thing' => array(
'counterCache' => 'views'
)
);
Then next time when you do addition/deletion via ItemView, Cake will automatically recalculate the counting and cache into views for you, so the next time when you do the query, you also need to make sure you specify recursive = -1 as what #Paco Car has suggested in his answer:
$this->Thing->recursive = -1;
$this->Thing->find(...); //this will returns array of Thing + the field "views"
// --- OR ---
$this->Thing->find(array(
'conditions' => array(
//... your usual conditions here
),
//... fields, order... etc
//this will make sure the recursive applies to this call, once only.
'recursive' => -1
);

Related

php dynamic vs manual array declaration

I have this situation where I could pre-define the array in this way:
$packages = array(
'0' => array(
'name' => 'Hotel1', //pcg name
'curr' => '$',
'amount' => '125',
'period' => 'NIGHT', //pcg duration
'client_data' => array(
'Name' =>'Adrien',
'Addr' =>'Sample Street',
'Payment' =>'Credit Card',
'Nights' =>'6',
)
),
);
Or
$packages = array();
$packages[] = array(
'name' => 'PREMIUM', //pcg name
'curr' => '$',
'amount' => '3.95',
'period' => 'MONTH', //pcg duration
'features' => array(
'Clients' =>'100',
'Invoices' =>'300 <small>MONTH</small>',
'Products' =>'30',
'Staff' =>'1',
)
);
The data will be static always so I wont be fetching this from
a sql query or a dynamic search. Would it make any difference
in terms of performance (the slightest difference could be helpful)
by using the first or the second "method" or they're actually 100%
identical in terms of performance.
Theorically the "dynamic" array creation might be slower because
it needs to check the size of the array, the last array index and
maybe other things such as those.
Thank you.
One simple task like that takes absolutely no resources in current hardware reality. Even in my first PC, a 386DX 20MHz it would not make that difference ;)
Anyway, I executed 1k times both options:
FIRST OPTION average:
0.000114s
SECOND OPTION average:
0.000108s
Be happy!

Why only last item is saved in database?

I have problem, becouse only last item from loop is saved in database.
Im using CakePhp 2.x
Controller:
for ($x=1; $x <= count($this->request->data['Goodsandoffer'])/3;$x++){
$promID = $this->request->data['Goodsandoffer']['promotionaloffer_id_'.$x];
if($this->request->data['Goodsandoffer']['cenaPromocyjna_'.$x] != ''){
$helperReqestTable3 = array('promotionaloffer_id'=>$this->request->data['Goodsandoffer']['promotionaloffer_id_'.$x],'good_id'=>$this->request->data['Goodsandoffer']['good_id_'.$x],'cenaPromocyjna'=>$this->request->data['Goodsandoffer']['cenaPromocyjna_'.$x]);
$helperReqestTable['Goodsandoffer']=$helperReqestTable3;
debug($helperReqestTable);
$this->Goodsandoffer->save($helperReqestTable);
}
}
Here is how look my debug in loop:
array(
'Goodsandoffer' => array(
'promotionaloffer_id' => '7',
'good_id' => '18',
'cenaPromocyjna' => '1'
)
)
And in next interation:
array(
'Goodsandoffer' => array(
'promotionaloffer_id' => '7',
'good_id' => '19',
'cenaPromocyjna' => '2'
)
)
In database is created only one row with last item.
Model:
class Goodsandoffer extends AppModel {
public $displayField = 'id';
public $belongsTo = array(
'Promotionaloffer' => array(
'className' => 'Promotionaloffer',
'foreignKey' => 'promotionaloffer_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Good' => array(
'className' => 'Good',
'foreignKey' => 'good_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
Have you tried calling $this->Goodsandoffer->create() before you call save? That way you're definitely telling Cake to create a new record each time.
The general process of creating and saving data in Cake is:
$this->Model->create()
$this->Model->set($data_array)
$this->Model->save()
You can also eliminate step 2 above by passing you $data_array to the save() function:
$this->Model->create();
$this->Model->save($data_array);
NOTE: from the manual (if you aren't using create()):
When calling save in a loop, don’t forget to call clear().
Another way you could create new data would be to make sure the primary key for your model is null in the data set you pass into save, although this is a little less obvious and probably best to stick to the create/[set/]save flow:
$data = array('id' => null, 'somefield' => 'foobar');
$this->Model->save($data); // new record created
In cakephp 2.0 $this->Model->create() create work fine. But if you are using cakephp version 3 or greater then 3. Then follow the below code
$saveData['itemId'] = 1;
$saveData['qty'] = 2;
$saveData['type'] = '0';
$saveData['status'] = 'active';
$saveData = $this->Model->newEntity($saveData);
$this->Model->save($materialmismatch);
In normal case we use patchEntity
$this->Model->patchEntity($saveData, $this->request->data);
It will only save last values of array so you have to use newEntity() with data

How do I create a CGridView with custom columns from a CArrayDataProvider?

I have an array of models that looks something like this. The models are extensions of the base Yii model class (BazClass), so that's a little bit of a custom solution, but I don't see why it shouldn't work.
$list = array
(
0 => FooClass#1
(
[BazClass:_attributes] => array
(
'FOO_ATTRIBUTE' => '4567'
'BAZ_ATTRIBUTE' => '1234'
'NAME' => 'FOO BAR'
)
[BazClass:_related] => array()
[_md] => null
[CModel:_errors] => array()
[CModel:_validators] => null
[CModel:_scenario] => ''
[CComponent:_e] => null
[CComponent:_m] => null
),
)
I made this a data provider by doing so:
$dataProvider = new CArrayDataProvider($list, array(
'pagination'=>array(
'pageSize'=>10,
),
));
$dataProvider->setData($list);
And try to render it in the view like so. Basically I'm just trying to show a list of the names, with the column named "Name".
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider,
'columns'=>array(
array(
'header' => 'Name',
'value' => $data->NAME,
),
),
));
The examples in the CGridView documentation make it look like that is possible, but the error I get is:
Either "name" or "value" must be specified for CDataColumn.
Well, I did specify a value, obviously, but it seems to be null. I also tried $data['NAME'] (because I somewhere read that the CArrayDataProvider doesn't return models), but it still evaluates to null.
I also checked that $dataProvider->getData() returns the same list as I passed it.
What gives?
you should place quotes around your value otherwise it gets interpreted in the wrong context
array( 'header' => 'Name', 'value' => '$data->NAME', ),

Cakephp - saveAll and beforeSave

Since CakePHP is updated to 2.3.5, I have problem to save data.
I don't want to save datas who contains no price.
My method beforeSave in my model looks like this :
public function beforeSave($options = array()){
if(empty($this->data['MyModel']['price'])){
unset($this->data['MyModel']);
}
return true;
}
Since my update, i've found this in /lib/Model/Model.php (l. 1751)
if ($success && $count === 0) {
$success = false;
}
If i comment this 3 lines my problem is solved. Do you know a method in my beforeSave who don't block my saving ?
If i use data validation, all my datas are not saved with my saveAll.
Exemple model "Option" validator:
public $validate = array(
'prix' => array(
'rule' => 'notEmpty'
)
);
Exemple datas to save :
array(
'Commande' => array(
'nature_commande' => '0',
'base_id' => '1',
'nom' => 'Test',
'chef_id' => '531',
),
'Option' => array(
(int) 0 => array(
'prix' => '5456'
),
(int) 1 => array(
'prix' => '45645'
),
(int) 3 => array(
'prix' => ''
)
)
saveAll in my Controller "Commande" (return false):
debug($this->Commande->saveAll($this->request->data, array('validate' => 'first')));
I would like my datas be saved out of the last row in my Model "Options" => 3.
I can use foreach in my controler to delete the blank row but can i make it better ?
Have you thought about using CakePHP's built-in data validation? This would allow you to specify that you don't want to save unless the 'price' is a certain amount, greater than zero, between a range...etc etc etc.
Basically, you set up rules for your fields, and any time you try to save a row, it makes sure each field passes whatever validation you set up.
It has a ton of validation types including valid email, number ranges, phone numbers, regex and more.

CakePHP findList doesn't return aggregated values

The following query returns an array containing the proper ids, but null for all values.
If I remove the aggregation function (AVG()), it returns values (not the averaged ones of course), if I choose e.g. find('all') it returns the average, but not in the list format I want (I could work with that, but I want to try to do it with 'list' first).
$progress = $this->Trial->find('list', array(
'fields' => array(
'Trial.session_id',
'AVG(Trial.first_reaction_time_since_probe_shown) AS average_reaction_time'
),
'group' => 'Trial.session_id',
'conditions' => array(
'Trial.first_valid_response = Trial.probe_on_top',
'TrainingSession.user_id IS NOT NULL'
),
'contain' => array(
'TrainingSession' => array(
'conditions' => array(
'TrainingSession.user_id' => $this->Auth->user('id')
)
)
),
'recursive' => 1,
));
The generated SQL query returns exactly the result I want, when I send it to the DB via PhpMyAdmin.
SELECT
`Trial`.`session_id`,
AVG(`Trial`.`first_reaction_time_since_probe_shown`) AS average_reaction_time
FROM
`zwang`.`trials` AS `Trial`
LEFT JOIN
`zwang`.`training_sessions` AS `TrainingSession` ON (
`Trial`.`session_id` = `TrainingSession`.`id` AND
`TrainingSession`.`user_id` = 1
)
WHERE
`Trial`.`first_valid_response` = `Trial`.`probe_on_top`
GROUP BY
`Trial`.`session_id`
I've examined the source for find('list'). I think it's due to the "array path" for accessing the list getting screwed up when using functions in the query, but I couldn't fix it yet (or recognise my abuse of CakePHP logic).
Once I posted the question, Stackoverflow started relating the correct answers to me.
Apparently, it can't be done with 'list' without virtualFields.
I didn't expect that because it worked using the other find-types.
$this->Trial->virtualFields = array(
'average_reaction_time' => 'AVG(Trial.first_reaction_time_since_probe_shown)'
);
$progress = $this->Trial->find('list', array(
'fields' => array('Trial.session_id','average_reaction_time')
/* etc... */
));

Categories