Yii mergeWith() in many-to-many relations - php

please help solve one trivial task for writing results:
tables:
flats :: id, dateadd
flatparams :: id, title
flatsoptions :: id, flatid, paramid, value
SQL DOWNLOAD HERE :: http://yadi.sk/d/034so9jIDgfoz
CONTROLLER CODE DOWNLOAD HERE :: http://yadi.sk/d/CtGAGZzyDghp7
I need to find flats that are in a region, city, street, apartment - for example:
russia, moscow, papanina, 8
My code looks for all the apartments, which have at least one of these parameters (if OR), and if the AND, then no result because trying to find a field in one table flatsoptions immediately all kinds of parameters. i would like to understand how to look flat on several parameters simultaneously. Thank you!
UPDATED :: 02-12-2013 22-00
$criteria = new CDbCriteria();
$criteria->together = true;
$i = 0;
foreach ($arr as $key => $item) {
if (in_array($key, $pos)) {
$criteria->join = 'LEFT JOIN flatsoptions AS flatsoptions'.$i.' ON flatsoptions'.$i.'.flatid = t.id';
$criteria->addColumnCondition(
array(
'flatsoptions'.$i.'.paramid' => $params[$key]['id'],
'flatsoptions'.$i.'.value' => $item
),
'AND',
'OR'
);
$i++;
}
}
$flats = Flats::model()->findAll($criteria);
And this code is not working - they not find the alias 'flatsoptions0' in 'where clause';
Updated :: SOLUTION
count($keys) = count($values);
This is a Mysql solution::
$criteria = new CDbCriteria();
$criteria->select = array('flatid');
$criteria->addInCondition('paramid', $keys);
$criteria->addInCondition('value', $values);
$criteria->group = 'flatid';
$criteria->having = 'count(*) = '.count($values);
$flats = Flatsoptions::model()->findAll($criteria);

In AND case you can join flatoptions N times (how many params passed), and use the code similar to:
$criteria->addColumnCondition(
array(
'flatsoptions1.paramid' => $params[$key1]['id'],
'flatsoptions1.value' => $item1
),
'AND',
'AND'
);
$criteria->addColumnCondition(
array(
'flatsoptions2.paramid' => $params[$key2]['id'],
'flatsoptions2.value' => $item2
),
'AND',
'AND'
);
...
$criteria->addColumnCondition(
array(
'flatsoptionsN.paramid' => $params[$keyN]['id'],
'flatsoptionsN.value' => $itemN
),
'AND',
'AND'
);
This is just an example. You need first define $criteria->with dynamically() with different aliases for the same relation and then put each addColumnCondition inside the loop.
LINKS:
CDbCriteria->with()
CActiveRecord->relations()

Related

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

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.

CakePHP - SQL query Join

Ok, so i'm in a pickle trying to figure a way for how to do this :
I have a live search(using ajax) which allows the user to select a criteria from a dropdown list and then enter a value which will be matched to values inside the database. This is quite trivial stuff, and on direct fields of the main model, i don't have any issues.
I have a Donor Model/table which consists of attributes such as ID, Name, Surname etc, but more importantly it also has FK of other associated models such as blood_group_id, donor_type_id, which map back to the respective models (BloodGroup and DonorType).. These two are already set with the associations and I am beyond that part, as I am already retrieving Donor records with associated model data.
Here is the search method which will hopefully help you in understanding my problem better.
public function search() {
if($this->request->is('post')){
if(!empty($this->request->data)){
$criteria = $this->request->data['criteria'];
$query = $this->request->data['query'];
$conditions = array("Donor." .$criteria. " LIKE '". $query . "%'");
The above checks if its a post request and whether data was sent. The criteria and user input are used to construct the query..
This is where my problem arises.. (When the user select search By blood type, as a criteria from the drop down)the above expects the user to enter the id of the blood_group rather than A+ or A- for instance.. So if the input is 1(id of blood group A+), the results are returned as expected. But I want the user to be able to enter A+...
Here is the rest of the method :
$this->Paginator->settings = array(
'conditions' => $conditions,
'limit' => 2
);
$donors = $this->Paginator->paginate('Donor');
$this->set('donors', $donors);
$this->beforeRender();
$this->layout= 'ajax';
}
}
}
I have tried this approach, setting up the conditions using the Model's name such as
if($criteria == 'blood_group_id'){
$conditions = array("BloodGroup.id" . " LIKE '". $query . "%'");
}elseif($criteria == 'donor_type_id'){
$conditions = array("DonorType.id" . " LIKE '". $query . "%'");
}else{
$this->Paginator->settings = array(
'conditions' => $conditions,
'limit' => 2
);
}
But this returns all the records irrespective of the input.
I also tried changing the settings for the paginator with no luck
$settings = array(
'joins' => array(
'table' => 'blood_groups',
'alias' => 'BloodGroup',
'type' => 'LEFT',
'conditions' => array(
"BloodGroup.id" => "Donor.blood_group_id",
"AND" => $conditions
)
),
'limit'=> 2
);
Any help on how to accomplish what I explained above, would greatly be appreciated!
simply:
if($criteria == 'blood_group_id')
$conditions = array("BloodGroup.name LIKE" => $query.'%');
(assuming bood_types has a 'name' column)
Also let me suggest you to use the CakeDC search plugin (https://github.com/CakeDC/search).

How can I search several word in several fields at once?

I am coding a search module. I do not know if I am ina good way but ti sound good for now excepted one point.
With the following code I can search only one word.
<?php
class SearchesController extends AppController{
function index(){
if($this->request->is('put') || $this->request->is('post') ){
// Save the send data
$search = $this->request->data['Search']['key'];
//$nbWords = str_word_count($search);
/*
THIS DOES NOT WORKS
if($nbWords > 1){
$searchTerms = explode(' ', $search);
$searchTermBits = array();
foreach ($searchTerms as $term) {
$term = trim($term);
if (!empty($term)) {
$searchTermBits[] = "Page.name LIKE '%$term%' AND Page.content LIKE '%$term%'";
}
}
}else{
$term = $search;
$searchTermBits[] = "Page.name LIKE '%$term%' AND Page.content LIKE '%$term%'";
}
*/
// SEARCH IN Pages TABLE
$this->loadModel('Page');
// SAVE THE RESULT IN
$d['pages'] = $this->Page->find('all', array(
'conditions' => array(
'online'=>'1',
'type'=>'page',
'created <= NOW()',
'id > 1',
'OR' => array(
//implode(' AND ', $searchTermBits)
'Page.name LIKE'=>'%'.$search.'%',
'Page.content LIKE'=>'%'.$search.'%'
)
),
'fields' => array('name','content','created','id','type')
));
//SEARCH IN Posts TABLE
$this->loadModel('Post');
$d['posts'] = $this->Post->find('all', array(
'conditions' => array(
'online'=>'1',
'type'=>'post',
'created <= NOW()',
'OR' => array(
'Post.name LIKE'=>'%'.$search.'%',
'Post.content LIKE'=>'%'.$search.'%'
)
),
'fields'=> array('name','content','created','id','type','slug')
));
// SEND THE VALUES TO THE VIEW
$this->set($d);
}else{
echo 'e';
}
}
}
The problem, I would like to be able to search with different words, for exeple "red car", "house monrain","web html5 development"
With my code, if I enter one word, it return a result, but if I add second word to the first (with a space) , it retuirn no result.
How can I change this
'OR' => array(
'Post.name LIKE'=>'%'.$search.'%',
'Post.content LIKE'=>'%'.$search.'%'
)
to make it able to search in 'name' and 'content' several words?
Thak for your help
Cheers
you can try this:
'OR' => array(
'Post.name' IN ('name1', 'name2'),
'Post.content' IN ('content1', 'content2')
)
Obviously you can put your value in array like this:
$name_arr = array();
$name_arr[] = 'name1';
$name_arr[] = 'name2';
and after:
'OR' => array(
'Post.name' IN ($name_arr),
'Post.content' IN ('content1', 'content2')
)
Change your $search variable
$search = $this->request->data['Search']['key'];
// if keywords "red car" then it will pass to your query %red%car%;
$search = str_replace(" ","%",$search);
Now it will search records having both words.
If you want to search with many words separated by a space, you can do this.
$searches = explode(" ", $search);
foreach ($searches as $search) {
//put your search condition in here
}
You might look into fulltext searching. If you setup your indices correctly, you can run queries such as:
SELECT id, MATCH (title,body) AGAINST ('Tutorial') FROM articles;

Foreach through a large table using Yii ActiveRecord - "out of memory" errors

I have a website on Yii Framework and I want to search a table for matching words.
I keep getting "out of memory" (it is a large table).
I try this code but it keeps loading the page
$dataProvider = new CActiveDataProvider('Data');
$iterator = new CDataProviderIterator($dataProvider);
foreach($iterator as $data) {
echo $data->name."\n";
}
So I try this code but it keeps limiting the result to 10:
$dataProvider = new CActiveDataProvider('Data');
$iterator = new CDataProviderIterator($dataProvider);
foreach($dataProvider as $data) {
echo $data->name."\n";
}
and if I do this I get the "out of memory" message:
$dataProvider = new CActiveDataProvider('Data' array(
'criteria'=>array(
'order'=>'id DESC',
),
'pagination' => false
));
foreach($dataProvider as $data) {
echo $data->name."\n";
}
I don't know why are you need to load all search results in one page, but you can change number of items per page to desire value by this code (and using pagination):
$dataProvider = new CActiveDataProvider('Data' array(
'criteria'=>array(
'order'=>'id DESC',
'condition' => 'town LIKE :search_town AND FK_country > :country_id',
'params' => array(':search_town' => $search_town.'%', ':country_id' => 10)
),
'pagination' => array(
"pageSize" => 100,
//"currentPage" => 0, //using for pagination
)
));
$iterator = new CDataProviderIterator($dataProvider);
foreach($iterator as $data) {
echo $data->name."\n";
}
Here http://sonsonz.wordpress.com/2011/10/14/yii-examples-of-using-cdbcriteria/ more examples
It is unwise to use CActiveDataProvider for big datasets. Especially if you only want to perform background tasks on them.
It would be advised to use direct SQL and go from there.
Based on the comments on CreatoR's answer, you are trying to find a number of occurences in a big table. As an example:
$connection=Yii::app()->db;
$sql = "SELECT id FROM data WHERE field1 LIKE '%someValue%' OR field2 LIKE '%someValue%' OR field3 LIKE '%someValue%'";
$command=$connection->createCommand($sql);
$numberOfRestuls=$command->execute();
//if you also want to display the results :
$ids=$command->queryAll();
$criteria=new CDbCriteria;
$criteria->addInCondition('id',$ids,'OR');
$dataProvider = new CActiveDataProvider('Data', $criteria);
//etc

CGridView filtering with aggregate (grouping) functions - eg MAX()

I have a CGridView that uses a MAX() mysql column to provide data for one of the columns. I have that working with sorting, but I can't figure out filtering. I assumed that I could just use CDbCriteria::compare() call to set it, but it's not working. Ideas?
My search function:
$criteria = new CDbCriteria;
$criteria->condition = 't.is_deleted = 0 and is_admin = 0';
// get last note date
$criteria->select = array('t.*', 'MAX(n.visit_date) AS last_note');
$criteria->join = 'LEFT JOIN notes n ON t.id = n.distributor_id';
$criteria->group = 't.id';
$criteria->order = 't.status';
$criteria->compare('t.type', $this->type);
$criteria->compare('t.name', $this->name, true);
$criteria->compare('t.city', $this->city, true);
$criteria->compare('t.state', $this->state);
$criteria->compare('last_note', $this->last_note);
return new CActiveDataProvider('Distributor', array(
'criteria' => $criteria,
'pagination' => array(
'pageSize' => 20
),
'sort' => array(
'defaultOrder' => 'name',
'attributes' => array(
'last_note' => array(
'asc' => 'last_note',
'desc' => 'last_note DESC'
),
)
),
));
In my view, I just have the name and value values set.
The error I get is CDbCommand failed to execute the SQL statement: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'last_note' in 'where clause'
Edit: The only way I found to do this is (because you can't have aggregate functions in where clauses) is to check the $_GET array for the last_note value, and add a having clause. Note, this isn't parameterized or anything yet, I just wanted to rough it out to see if it would work:
if(isset($_GET['Distributor']['last_note']) && $_GET['Distributor']['last_note'] != '') {
$criteria->having = 'MAX(n.visit_date)=\'' . $this->last_note . "'";
}
I hate using the request variables in the model like this, but there isn't much else I could do.
You're on the right track. You filter aggregate functions like MAX() and SUM() using having. In a query like this, you should probably add a group, otherwise you'll end up with a lot of duplicated data in your result when a Distributor has multiple notes on the same day (assuming t.id is a primary key). Second, you should use named params instead of putting variables directly into SQL. So, instead of compare('last_note'... you'd end up with something like this:
$criteria->group = 't.id';
$criteria->having = 'MAX(n.visit_date) = :last_note';
$criteria->params = array(':last_note' => $this->last_note);
In order for filtering to work, you should have a class attribute to store the data:
public $last_note;
otherwise $this->last_note wont return anything and your $criteria->compare('last_note', $this->last_note); wont do anything either

Categories