Getting dependent records in $dataProvider variable - php

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')
)
)
);

Related

Yii relations subquery

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'
),
);
}

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'
)
));

cakephp - getting joins correct

I'm a bit of a newbie and I'm having trouble getting my cakephp query correct.
I have a users table and users hasmany rotas. The rotas table looks like
monday_am
monday_pm
tuesday_am
etc
and if the user is due to be in on monday_pm for example, then that field will have 1. Otherwise 0.
I want to get all the users who belong to a particular room, have deregistered=0 and - and this is where I'm having the problem - who have a rota where (for example) tuesday_am = 1
So I have the code:
$options['conditions'] = array('User.room_id'=>$room_id, 'User.deregistered'=>0, 'User.request'=>0);
$options['joins'] = array(
array('table' => 'rotas',
'alias' => 'rota',
'type' => 'INNER',
'conditions' => array('rota.'.$todaysDay.'_'.$amPm => 1)
)
);
$usersToday = $this->User->find('all', $options);
If I don't try the join then I get a nice small list of users like I expect. If I try the join I get 100s of results which are mostly duplicates!
How can I get just one record of a user who has the required conditions in the Users table as well as the Rotas table, and how come doing the join as I have causes so many results?!?
thanks
Assuming you have a column in your rota table called user_id, you can make your Rota model like this:
class Rota extends Model
{
public $belongsTo = array('User');
}
Then in your controller, you can query the Rotas and get the user from it.
$users = $this->Rota->find('all', array(
'fields' => array('DISTINCT (User.id)' /* add more fields for selection */ ),
'conditions' => array(
'User.room_id'=>$room_id,
'User.deregistered'=>0,
'User.request'=>0,
'Rota.'.$todaysDay.'_'.$amPm => 1
)
));

How can I find all records for a model without doing a long list of "OR" conditions?

I'm having trouble composing a CakePHP find() which returns the records I'm looking for.
My associations go like this:
User ->(has many)-> Friends ,
User ->(has many)-> Posts
I'm trying to display a list of all a user's friends recent posts, in other words, list every post that was created by a friend of the current user logged in.
The only way I can think of doing this is by putting all the user's friends' user_ids in a big array, and then looping through each one, so that the find() call would look something like:
$posts = $this->Post->find('all',array(
'conditions' => array(
'Post.user_id' => array(
'OR' => array(
$user_id_array[0],$user_id_array[1],$user_id_array[2] # .. etc
)
)
)
));
I get the impression this isn't the best way of doing things as if that user is popular that's a lot of OR conditions. Can anyone suggest a better alternative?
To clarify, here is a simplified version of my database:
"Users" table
id
username
etc
"Friends" table
id
user_id
friend_id
etc
"Posts" table
id
user_id
etc
After reviewing what you have rewritten, I think I understand what you are doing. Your current structure will not work. There is no reference in POSTS to friends. So based on the schema you have posted, friends CANNOT add any POSTS. I think what you are trying to do is reference a friend as one of the other users. Meaning, A users FRIEND is actually just another USER in the USERS table. This is a self referential HABTM relationship. So here is what I would propose:
1- First, make sure you have the HABTM table created in the DB:
-- MySQL CREATE TABLE users_users ( user_id char(36) NOT NULL,
friend_id char(36) NOT NULL );
2- Establish the relationships in the User model.
var $hasAndBelongsToMany = array(
'friend' => array('className' => 'User',
'joinTable' => 'users_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'friend_id',
'unique' => true,
),
);
var $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'user_id'
),
);
3- use the scaffolding to insert a few records, linking friends and adding posts.
4- Add the view record function to the Users controller:
function get_user($id)
{
$posts = $this->User->find('first', array(
'conditions' => array('User.id' => $id),
'recursive' => '2'
));
pr($posts);
}
5- Now you can query the User table using recursive to pull the records using the following command:
http://test/users/get_user/USER_ID
6- Your output will show all of the records (recursively) including the friends and their posts in the returned data tree when you pr($posts)
I know this is a long post, but I think it will provide the best solution for what you are trying to do. The power of CakePHP is incredible. It's the learning curve that kills us.
Happy Coding!
If Post.user_id points to Friend.id (which wouldn't follow the convention btw) then it would be
$posts = $this->Post->find('all',array(
'conditions' => array(
'Post.user_id' => $user_id_array
)
);
which would result in .. WHERE Post.user_id IN (1, 2, 3) ..
Depending on your setup, it might be quicker to run two queries rather than trying to chain them together via the Cake stuff. I'd recommend adding something like getFriendsPosts() in the Users model.
<?php
class UserModel extends AppModel {
// ... stuff
function getFriendsPosts( $user_id )
{
$friends = $this->find( ... parameters to get user IDs of all friends );
// flatten the array or tweak your params so they fit the conditions parameter. Check out the Set class in CakePHP
$posts = $this->find( 'all', array( 'conditions' => array( 'User.id' => $friends ) ) );
return $posts;
}
}
?>
Then to call it, in the controller just do
$friends = $this->User->getFriendsPosts( $this->Auth->User('id') );
HTH,
Travis
Isn't CakePHP already generating the efficient code of:
SELECT * from Posts WHERE user_id IN (id1, id2 ...)
if not, you can do
$conditions='NULL';
foreach($user_id_array as $id) $conditions.=", $id";
$posts = $this->Posts->find('all', array(
'conditions' => "Post.user_id IN ($conditions)",
));
If your models are properly associated, Cake will automatically retrieve related model records. So, when you search for a specific user, Cake will automatically retrieve related friends, and related posts of these friends. All you need to do is set the recursion level high enough.
$user = $this->User->find('first', array('conditions' => array('User.id' => $id), 'recursive' => 2));
debug($user);
// gives something like:
array(
User => array()
Friend => array(
0 => array(
...
Post => array()
),
1 => array(
...
Post => array()
)
)
)
All you need to do is extract the posts from the user's friends, which is as easy as:
$postsOfFriends = Set::extract('/Friend/Post/.', $user);

Categories