Yii relations subquery - php

This is the query I have:
select * from order_shipping_date osd inner join
(SELECT MAX(osd.id) as id FROM order_shipping_date osd
group by osd.order_id) osdi ON osd.id = osdi.id
I'm fine with keeping it so, but would like to make it possible to define it as relations
This is more comfy to use such code later
Is this doable at all? I can't find any examples.

Additional options can be specified in relationship declaration.
public function relations()
{
return array(
'orderShippingDate' => array(
// define you relation
'join' => '(/* subquery here*/) osdi ON osdi.id=orderShippingDate.id',
'joinType' => 'INNER JOIN'
),
);
}

Related

Yii CSqlDataProvider with relations

I have an complex query with joins and conditions
and I get data with CSqlDataProvider
But I also need to join relational table records.
Lets say, we have table A (products) and table B (product_modifications)
I need to list products along with their modifications..
I get data from table A, and i need also to get some records from table B
for each record in table A, query should get an array from table B
Basic code:
class Product extends CActiveRecord
{
//some code
public function relations()
{
return array(
'modifications' => array(self::HAS_MANY, 'Modification', 'modification_product_id'),
);
}
//some code
}
my query
$sql = Yii::app()->db->createCommand();
...//different joins and conditions
$this->dataProvider = new CSqlDataProvider($sql->text, array(
'keyField' => 'product_id',
'pagination' => array('pageSize' => 20),
));
How can i join records from table B (product_modifications)?
In CActiveDataProvider its like:
$this->dataProvider = new CActiveDataProvider ('products', array(
'pagination' => array('pageSize' => 20),
'criteria' => array(
'with' => array(
'modifications' => array('condition' => 'some condition',),
),
),
));
But i dont know how to do this with CSqlDataProvider
UPD:
solved by converting query to corresponding CActiveDataProvider query
You can't use AR-relation with sql commands, because it is absolutely different tools for work with Db. In ActiveRecord you are using relation, in Sql commands you are using sql-joins. That's mean you should add you condition there with join:
$sql = Yii::app()->db->createCommand();
...//different joins and conditions + your condition

CakePHP 2.x retrieve/query using data in HABTM join table

I have 2 tables joined by a HABTM relationship. portfolios and assets. The join table is portfolios_assets.
I wish to have an extra field in the join table, rebalance_date such that I can query the assets in a portfolio for a given date. How would I construct a find such that I can determine the most recent date and only return the assets for that date.
So, in my Portfolio model I might have:
$params = array(
'conditions' => array(
'Portfolio.id' => 5,
'?.rebalance_date' => '2013-11-01' //no model name for this field as it's in the join table
),
'order' => array(...)
);
$result = $this->find('all', $params);
In the above example, I just keyed in a date. I'm not sure how I would retrieve the latest date without writing a raw query in Cake. (I could do SELECT rebalance_date FROM portfolios_assets ORDER BY rebalance_date DESC LIMIT 1; but this is not following Cake's convention)
You need to use the hasMany through model: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany-through-the-join-model
You would need to create another model eg PortfolioAssests and the table would need to be portfolio_assets not portfolios_assets.
Then you should be able to use:
$assets = $this->Assets->find('all', array(
'conditions' => array(
'Portfolio.id' => 5,
'PortfolioAsset.rebalance_date' => '2013-11-01'
)
));

how to set a relation with empty value?

I have 2 tables namely Equipment and Supply.
I have used array_merge in yii php to serve as union for two different tables for the purpose of diplaying them in a single grid.
Everything works fine with the fields that is common with the two tables. The problem is when I try to display a field that is only existing in one of these two tables. It says Property "Supply.equipType" is not defined" because only equipment has the equipType relation.
in my gridView:
array(
'name'=>'equipment_type',
'value'=>'$data->equipType->name',
),
in my controller where I did the merging:
$prov1 = new CActiveDataProvider('BaseEiEquipItem', array(
'criteria' => array(
'condition' => 'id>0'
)));
$prov2 = new CActiveDataProvider('BaseSiReceivedItem', array(
'criteria' => array(
'condition' => 'id>0'
)));
$records=array_merge($prov1->data , $prov2->data);
$provAll = new CArrayDataProvider($records,
array(
'sort' => array( //optional and sortring
'keyField'=>false,
'attributes' => array(
'id', 'description',),
),
'pagination' => array('pageSize' => 10) //optional add a pagination
)
);
$this->render('create',array(
'model'=>$model,
'searchModel'=>$searchModel,
'modelGrid'=>$modelGrid,
'provAll' => $provAll,
));
in my equipment model :
public function relations() {
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'equipType' => array(self::BELONGS_TO, 'BaseRefEquipmentType', 'equipment_type'),
);
}
Any idea on how to solve this? Is there any way to fake a relation or something?
thanks in advance
Just add condition to check if equipType is there:
'value'=>'isset($data->equipType) ? $data->equipType->name : ""'
Using sql to merge results would be better, though.
Can't you just use plain SQL union?
If Equipment has 2 fields (a, b) and Supply has 2 fields (b, c), you can do union like this:
SELECT a, b, null FROM Equipment
UNION ALL
SELECT null, b, c FROM Supply
This problem shows, that it's probably not a good idea to mix different model types in one gridview. If you have a model that has no equipType then it's pretty obvious, that you can't show this column in a gridview. So what would you expect?
As a (dirty) workaround you can add all missing columns as pseudo attributes to the models where they are missing:
public $equipType;

Getting dependent records in $dataProvider variable

i have two tables tbl_entries and tbl_votings
tbl_entries -> id, othercolums
tbl_votings -> id, entry_id, othercolumns
i want to show data in zii.widgets.CListView from the tbl_entries if users have voted for the entries.
i am able to run below sql query successfully.
select * from tbl_entries where id in (select tbl_entries.id from tbl_votings where entry_id = tbl_entries.id )
how can i do in YII style so that i can show result in CListView?
in Entries model add relation:
...
'votes' => array(self::HAS_MANY, 'Votings', 'entry_id'),
...
Then search via AR:
$records = Entries::model()->with('votes')->findAll();
Hope this helps.
Updated
Ok, i missed word "if" in your post, and thought you need entries ONLY if users voted for it. Read update below for right code. I leave you the first part just in case you need it in the future. INNER JOIN will take entries ONLY if that entries have at least 1 vote.
------------------ FIRST PART (wrong) --------------------
Declare a relation inside your Entries model:
public function relations() {
return array(
'votes' => array(self::HAS_MANY, 'Votings', 'entry_id'),
);
}
And when you create your data provider do it like this:
$dataProvider = new CActiveDataProvider('Entries',
array(
'criteria' => array(
'with' => array(
'votes'=>array(
'joinType' => 'INNER JOIN'
)
)
)
)
);
This will not create you the SQL you have written above, but this will do it the right way.
The SQL will look similar to this:
select * from tbl_entries t INNER JOIN tbl_votings v ON t.id = v.entry_id
----------------- SECOND PART (right) ------------------
Update
Ok, so if you simply need to get entries and votes for them you do the same relation declaration from first part:
public function relations() {
return array(
'votes' => array(self::HAS_MANY, 'Votings', 'entry_id'),
);
}
And you create your data provider like this:
$dataProvider = new CActiveDataProvider('Entries',
array(
'criteria' => array(
'with' => array('votes')
)
)
);

CakePHP - problem with HABTM paginate query

Tables
restaurants
cuisines
cuisines_restaurants
Both restaurant and cuisine model are set up to HABTM each other.
I'm trying to get a paginated list of restaurants where Cuisine.name = 'italian' (example), but keep getting this error:
1054: Unknown column 'Cuisine.name' in 'where clause'
Actual query it's building:
SELECT `Restaurant`.`id`, `Restaurant`.`type` .....
`Restaurant`.`modified`, `Restaurant`.`user_id`, `User`.`display_name`,
`User`.`username`, `User`.`id`, `City`.`id`,`City`.`lat` .....
FROM `restaurants` AS `Restaurant` LEFT JOIN `users` AS `User` ON
(`Restaurant`.`user_id` = `User`.`id`) LEFT JOIN `cities` AS `City` ON
(`Restaurant`.`city_id` = `City`.`id`) WHERE `Cuisine`.`name` = 'italian'
LIMIT 10
The "....." parts are just additional fields I removed to shorten the query to show you.
I'm no CakePHP pro, so hopefully there's some glaring error. I'm calling the paginate like this:
$this->paginate = array(
'conditions' => $opts,
'limit' => 10,
);
$data = $this->paginate('Restaurant');
$this->set('data', $data);
$opts is an array of options, one of which is 'Cuisine.name' => 'italian'
I also tried setting $this->Restaurant->recursive = 2; but that didn't seem to do anything (and I assume I shouldn't have to do that?)
Any help or direction is greatly appreciated.
EDIT
models/cuisine.php
var $hasAndBelongsToMany = array('Restaurant');
models/restaurant.php
var $hasAndBelongsToMany = array(
'Cuisine' => array(
'order' => 'Cuisine.name ASC'
),
'Feature' => array(
'order' => 'Feature.name ASC'
),
'Event' => array(
'order' => 'Event.start_date ASC'
)
);
As explained in this blogpost by me you have to put the condition of the related model in the contain option of your pagination array.
So something like this should work
# in your restaurant_controller.php
var $paginate = array(
'contain' => array(
'Cuisine' => array(
'conditions' => array('Cuisine.name' => 'italian')
)
),
'limit' => 10
);
# then, in your method (ie. index.php)
$this->set('restaurants', $this->paginate('Restaurant'));
This fails because Cake is actually using 2 different queries to generate your result set. As you've noticed, the first query doesn't even contain a reference to Cuisine.
As #vindia explained here, using the Containable behavior will usually fix this problem, but it doesn't work with Paginate.
Basically, you need a way to force Cake to look at Cuisine during the first query. This is not the way the framework usually does things, so it does, unfortunately, require constructing the join manually
. paginate takes the same options as Model->find('all'). Here, we need to use the joins option.
var $joins = array(
array(
'table' => '(SELECT cuisines.id, cuisines.name, cuisines_restaurants.restaurant_id
FROM cuisines_restaurants
JOIN cuisines ON cuisines_restaurants.cuisines_id = cuisines.id)',
'alias' => 'Cuisine',
'conditions' => array(
'Cuisine.restaurant_id = Restaurant.id',
'Cuisine.name = "italian"'
)
)
);
$this->paginate = array(
'conditions' => $opts,
'limit' => 10,
'joins' => $joins
);
This solution is a lot clunkier than the others, but has the advantage of working.
a few ideas on the top of my mind:
have you checked the model to see if the HABTM is well declared?
try using the containable behavior
in none of those work.. then you could always construct the joins for the paginator manually
good luck!
Cuisine must be a table (or alias) on the FROM clausule of your SELECT.
so the error:
1054: Unknown column 'Cuisine.name' in 'where clause'
Is just because it isn't referenced on the FROM clausule
If you remove the Feature and Event part of your HABTM link in the Restaurant model, does it work then?
Sounds to me like you've failed to define the right primary and foreing keys for the Cuisine model, as the HABTM model is not even including the Cuisine tabel in the query you posted here.

Categories