Cakephp joining tables via find option not working - php

I'm trying to join a Users table to my curent hasMany Through table which has Interest and User model id's.
Below is the find query with options:
$params = array(
'fields' => array('*', 'COUNT(DISTINCT(InterestsUser.interest_id)) as interest_count'),
'limit' => 15,
'recursive' => -1,
'offset' => $offset,
'conditions' => array('InterestsUser.interest_id' => $conditions),
'group' => array('InterestsUser.user_id'),
'order' => array('interest_count DESC', 'InterestsUser.user_id ASC', 'InterestsUser.interest_id ASC'),
'joins' => array(
array('table' => 'users',
'alias' => 'User',
'type' => 'LEFT',
'conditions' => array(
'User.id' => 'InterestsUser.user_id',
)
)
)
);
$results = $this->InterestsUser->find('all', $params);
This returns InterestsUser table fine but does not return any values for Users table. It only returns field names.
What could be wrong?
UPDATE:
OK, above is generating below SQL which I got from Cake's datasources sql log:
SELECT *, COUNT(DISTINCT(InterestsUser.interest_id)) as interest_count
FROM `interests_users` AS `InterestsUser`
LEFT JOIN users AS `User` ON (`User`.`id` = 'InterestsUser.user_id')
WHERE `InterestsUser`.`interest_id` IN (3, 2, 1)
GROUP BY `InterestsUser`.`user_id`
ORDER BY `interest_count` DESC, `InterestsUser`.`user_id` ASC, `InterestsUser`.`interest_id` ASC
LIMIT 15
Why is users table values returning NULL only for all fields?
UPDATE:
OK I tried below but this is working fine...What am I missing here!!??
SELECT * , COUNT( DISTINCT (
interests_users.interest_id
) ) AS interest_count
FROM interests_users
LEFT JOIN users ON ( users.id = interests_users.user_id )
WHERE interests_users.interest_id
IN ( 1, 2, 3 )
GROUP BY interests_users.user_id
ORDER BY interest_count DESC
LIMIT 15

The array syntax for join conditions should be like the following
array('User.id = InterestsUser.user_id')
as opposed to array('User.id' => 'InterestsUser.user_id'). For more, see http://book.cakephp.org/view/1047/Joining-tables.

Related

Checking if selected rows are favourite by user in cakephp

I have a Venue table with 20+ columns.
I also have a favorite_venues table with columns
id | userId | venueId
Here is my code to get venues
$this->Venue->virtualFields = array(
'bookedCount' => "SELECT count(*) FROM bookings WHERE venueId = Venue.id"
);
$result = $this->Venue->find('all', array('conditions'=>$conditions,
'order' => array('Venue.bookedCount DESC'),
'limit' => 20,
'offset' => $offset * 20
));
i want to add a condition if i send userId, it should check every venue if its added to favourite list or not and set
$venue['isFavorite'] = yes/no
I dont want a for loop and check every venue i get. is there any way i can incorporate in same Mysql query in cakephp. I am not sure how to put yes/no as virtual field
You can LEFT join in your favorite_venues table, and then for example use a CASE statement in a virtual field that checks whether there is a linked row.
Here's an untested example to illustrate what I mean. I don't know how the user ID is involved, so you got to figure that on your own.
$this->Venue->virtualFields = array(
'bookedCount' => "SELECT count(*) FROM bookings WHERE venueId = Venue.id",
'isFavorite' => 'CASE WHEN FavoriteVenues.id IS NOT NULL THEN "yes" ELSE "no" END'
);
$result = $this->Venue->find('all', array(
'conditions' => $conditions,
'order' => array('Venue.bookedCount DESC'),
'limit' => 20,
'offset' => $offset * 20,
'joins' => array(
array(
'table' => 'favorite_venues',
'alias' => 'FavoriteVenues',
'type' => 'LEFT',
'conditions' => array(
'FavoriteVenues.venueId = Venue.id',
)
)
),
// don't forget to group to prevent duplicate results
'group' => 'Venue.id'
));
See also
Cookbook > Models > Virtual Fields > Virtual fields set in controller with JOINS
Cookbook > Models > Associations: Linking Models Together > Joining Tables
MySQL 5.7 Reference Manual / SQL Statement Syntax / ... / CASE Syntax

CakePHP Translate: Getting group by latest date

How do I translate below to cakePHP code? The code below comes from a solution here MySQL order by before group by.
Getting the author's latest post using group by.
SELECT p1.*
FROM wp_posts p1
INNER JOIN
(
SELECT max(post_date) MaxPostDate, post_author
FROM wp_posts
WHERE post_status='publish'
AND post_type='post'
GROUP BY post_author
) p2
ON p1.post_author = p2.post_author
AND p1.post_date = p2.MaxPostDate
WHERE p1.post_status='publish'
AND p1.post_type='post'
order by p1.post_date desc
Below is still similar situation:
SELECT t1.* FROM payment_status t1
JOIN (SELECT payment_id, MAX(created) max_created
FROM payment_status
GROUP BY payment_id
) t2
ON t1.payment_id = t2.payment_id AND t1.created = t2.max_created;
I need some cakePHP translation for either the two mySQL statements.
------------------------------------------------------------------------------------
I did something like the code below but it gives me error
Error: SQLSTATE[42000]: Syntax error or access violation: 1059 Identifier name 'SELECT max(dateEncoded) maxDate, findings FROM maintain GROUP BY computer_id Office.main_office LIKE' is too long
How do I fix it?
$this->Computer->unbindModel(array(
'belongsTo' => array('Office'),
'hasMany' => array('Brand','Maintain')
));
$model_view = $this->Computer->bindModel(array(
'hasOne' => array(
'Office' => array(
'foreignKey' => false,
'conditions' => array('Office.id = Computer.office_id')
),
'Maintain' => array(
'foreignKey' => false,
'conditions' => array('Computer.id = Maintain.computer_id'),
)
)
)
);
$main_office = trim($this->request->data['Office']['office_id']);
$joins = array(
array(
'table' => "SELECT max(dateEncoded) maxDate, findings FROM maintain GROUP BY computer_id",
'alias' => 'P2',
'type' => 'INNER',
'conditions' => array('Maintain.findings = p2.findings','Maintain.dateEncoded = p2.maxDate')
)
);
$conditions=array("Office.main_office LIKE"=>"%$main_office%");
$result = $this->Computer->find('all',array(
$model_view,
'joins'=>$joins,
'conditions'=>$conditions,
'order' => array('Office.description'),
'group' => 'Computer.id'
));
This can be written something like this :-
$joins = array(
array(
'table' => 'SELECT max(post_date) MaxPostDate, post_author FROM wp_posts WHERE post_status='publish' AND post_type='post'GROUP BY post_author',
'alias' => 'P2',
'type' => 'INNER',
'conditions' => array('WpPost.post_author = p2.post_author','WpPost.post_date = p2.MaxPostDate')
)
);
$conditions=array("WpPost.post_status='publish'","WpPost.post_type='post'");
$this->WpPost->find('all',array('fields'=>array('WpPost.*'),'joins'=>$joins,'conditions'=>$conditions);

CakePHP pagination - order by virtual field

Contributors have songs and songs have contributors. I want to be able to sort by the number of songs that a contributor has.
In my Controller:
public $paginate = array(
'fields' => array(
'Contributor.id',
'Contributor.name',
'COUNT(DISTINCT ContributorsSong.song_id) AS Contributor__TotalSongs',
),
'joins' => array(
array(
'alias' => 'ContributorsSong',
'table' => 'contributors_songs',
'type' => 'LEFT',
'conditions' => 'ContributorsSong.contributor_id = Contributor.id'
),
array(
'alias' => 'Song',
'table' => 'songs',
'type' => 'LEFT',
'conditions' => 'ContributorsSong.song_id = Song.id'
)
),
'group' => array('ContributorsSong.contributor_id')
);
And in my index method.
$this->Contributor->recursive = 0;
$this->Paginator->settings = $this->paginate;
$this->Contributor->virtualFields['TotalSongs'] = 0;
$items = $this->paginate();
echo '<pre>';print_r($items);echo '</pre>';
I'm trying to sort by the number of songs by using a virtual field, so when I go to
localhost/site/contributors/index/sort:TotalSongs/
I get this error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column '0' in 'order clause'
SQL Query:
SELECT Contributor.id,
Contributor.name,
COUNT(DISTINCT ContributorsSong.song_id) AS Contributor__TotalSongs
FROM db_songs2.contributors AS Contributor LEFT JOIN
db_songs2.contributors_songs AS ContributorsSong ON
(ContributorsSong.contributor_id = Contributor.id) LEFT JOIN
db_songs2.songs AS Song ON (ContributorsSong.song_id =
Song.id) WHERE 1 = 1 GROUP BY ContributorsSong.contributor_id
ORDER BY (0) desc LIMIT 18
I thought that TotalSongs would get turned into Contributor__TotalSongs in the query but it gets turned into 0. What is going on here? Thanks.
I replaced:
$this->Contributor->virtualFields['TotalSongs'] = 0;
with:
$this->Contributor->virtualFields['TotalSongs'] = 'COUNT(DISTINCT ContributorsSong.song_id)';
And deleted:
'COUNT(DISTINCT ContributorsSong.song_id) AS Contributor__TotalSongs',
And it works for ordering now, but I can't use it in the conditions. I think this is part of the Limitations of virtualFields? I'm trying to select only tables where TotalSongs > 0, but I think I can get this another way, by changing the join from LEFT to INNER.

CakePHP: adding DISTINCT to find causes an associated to get omitted

We build a fairly complex set of $conditions, then we search using Paginator:
$this->Paginator->settings = array( 'conditions'=>$conditions,
'joins'=>$joins,
'limit'=>25,
//'fields' => array('DISTINCT (User.id)')
);
$resume_display_array = $this->Paginator->paginate('User');
You'll see the 'fields' is commented out. When we uncomment that field, it causes associated model Resume to get dropped from the results array (without it, each result returned includes a [Resume] key.
Why is this happening?
Here's the SQL dump WITH the DISTINCT option in use:
SELECT DISTINCT (`User`.`id`), `User`.`id`
FROM `sw`.`users` AS `User`
LEFT JOIN `sw`.`taggings_users` AS `TaggingUser`
ON (`User`.`id`= `TaggingUser`.`user_id`)
LEFT JOIN `sw`.`taggings` AS `Tagging`
ON (`TaggingUser`.`tagging_id`= `Tagging`.`id`)
LEFT JOIN `sw`.`resumes` AS `Resume`
ON (`Resume`.`user_id` = `User`.`id`)
WHERE `User`.`first_name` LIKE '%jordan%' AND NOT (`Tagging`.`tag_name` = ('done'))
LIMIT 25
Here's the SQL dump WITHOUT the DISTINCT option:
SELECT `User`.`id`, `User`.`username`, `User`.`password`, `User`.`role`, `User`.`created`, `User`.`modified`, `User`.`email`, `User`.`wordpress_user_id`, `User`.`first_name`, `User`.`last_name`, `User`.`group_id`, `Resume`.`id`, `Resume`.`user_id`, `Resume`.`wordpress_resume_id`, `Resume`.`wordpress_user_id`, `Resume`.`has_file`, `Resume`.`is_stamped`, `Resume`.`is_active`, `Resume`.`file_extension`, `Resume`.`created`, `Resume`.`modified`, `Resume`.`is_deleted`
FROM `sw`.`users` AS `User`
LEFT JOIN `sw`.`taggings_users` AS `TaggingUser`
ON (`User`.`id`= `TaggingUser`.`user_id`)
LEFT JOIN `sw`.`taggings` AS `Tagging`
ON (`TaggingUser`.`tagging_id`= `Tagging`.`id`)
LEFT JOIN `sw`.`resumes` AS `Resume`
ON (`Resume`.`user_id` = `User`.`id`)
WHERE `User`.`first_name` LIKE '%jordan%' AND NOT (`Tagging`.`tag_name` = ('done'))
LIMIT 25
Update
Here is a var_dump of $joins:
array(
(int) 0 => array(
'table' => 'taggings_users',
'type' => 'LEFT',
'alias' => 'TaggingUser',
'conditions' => array(
(int) 0 => 'User.id= TaggingUser.user_id'
)
),
(int) 1 => array(
'table' => 'taggings',
'type' => 'LEFT',
'alias' => 'Tagging',
'conditions' => array(
(int) 0 => 'TaggingUser.tagging_id= Tagging.id'
)
)
)
If you specify the fields option, you need to specify all fields that you want to retrieve.
By setting it ONLY to User.id, you're telling it that that is the ONLY field you want returned.
You could also try something like below as a catchall (not sure if that works, but if not, you get the idea - you're limiting your own results):
'fields' => array('*', 'DISTINCT (User.id)')

CakePHP not joining the correct tables in find query

OK, another cake question from me.
I have rather a complex table structure with quite a few joins. Here's the jist of it:
So, in summary:
RiskCategory $hasMany Client [Client $belongsTo RiskCategory]
Client $hasMany Account [Account $belongsTo Client]
Account $hasMany Holding [Holding $belongsTo Account]
In my RiskCategory model I want to run a query that basically returns the sum of the holdings in each riskCategory id, grouped by date. There's a few other conditions but here is what I've put together:
$findParams = array(
'recursive' => 4,
'fields' => array(
'Holding.holding_date',
'SUM(Holding.value) AS risk_category_value'
),
'group' => array('Holding.holding_date'),
'order' => 'Holding.holding_date ASC'
);
$findParams['conditions'] = array(
'Client.active' => true,
'Client.model' => true,
'Client.currency' => 'GBP',
'OR' => array(
'Holding.holding_date' => $this->end_date,
array(
'Holding.holding_date = LAST_DAY(Holding.holding_date)',
'MONTH(Holding.holding_date)' => array(3,6,9,12)
)
)
);
$valuations = $this->Client->Account->Holding->find( 'all', $findParams );
When I run the above cake is giving me an error saying various fields are not present in the query. The raw query created is as follows:
SELECT `Holding`.`holding_date`,
Sum(`Holding`.`value`) AS risk_category_value
FROM `ips_client_db`.`holdings` AS `Holding`
LEFT JOIN `ips_client_db`.`accounts` AS `Account`
ON ( `Holding`.`account_id` = `Account`.`id` )
LEFT JOIN `ips_client_db`.`sedols` AS `Sedol`
ON ( `Holding`.`sedols_id` = `Sedol`.`id` )
WHERE `client`.`active` = '1'
AND `client`.`model` = '1'
AND `client`.`currency` = 'GBP'
AND ( ( `Holding`.`holding_date` = '2013-10-01' )
OR (( ( `Holding`.`holding_date` = Last_day(
`Holding`.`holding_date`) )
AND ( Month(`Holding`.`holding_date`) IN ( 3, 6, 9, 12 ) ) )
) )
GROUP BY `Holding`.`holding_date`
ORDER BY `Holding`.`holding_date` ASC
It looks as though cake is not doing all the joins. It is only joining Account to Holding and then Holding to Sedol (which is another joined table in the database but is not needed for this query so I've omitted it from the diagram)
Why are the joins not being made properly and how to acheive this? I'd like to avoid writing a raw statement if possible.
EDIT: The joins should be as follows:
...
FROM risk_categories
LEFT JOIN ((clients
LEFT JOIN accounts
ON clients.id = accounts.client_id)
LEFT JOIN holdings
ON accounts.id = holdings.account_id)
ON risk_categories.id = clients.risk_category_id
...
CakePHP does not perform deep joins for associations that are more than 1 level deep. That is why you're getting an error for references to the Client table in the conditions.
For these types of problems it's easier to use the Containable behavior. I usually add this behavior to my AppModel as the default handler for associations.
Containable allows you to define the associations (only those already defined) with their fields and conditions as part of the find operation. This is done by adding a new contain key in the find parameters. Below I've taking a guess at what you might need.
$findParams = array(
'fields' => array(
'Holding.holding_date',
'SUM(Holding.value) AS risk_category_value'
),
'conditions'=>array(
'OR' => array(
'Holding.holding_date' => $this->end_date,
array(
'Holding.holding_date = LAST_DAY(Holding.holding_date)',
'MONTH(Holding.holding_date)' => array(3,6,9,12)
)
)
),
'group' => array('Holding.holding_date'),
'order' => 'Holding.holding_date ASC',
'contain'=>array(
'Account'=>array(
'Client'=>array(
'RiskCategory',
'conditions'=>array(
'Client.active' => true,
'Client.model' => true,
'Client.currency' => 'GBP',
)
)
)
)
);
$valuations = $this->Client->Account->Holding->find( 'all', $findParams );

Categories