CakePHP multiple JOIN findAll Conditions issue - cakephp

Here is my complex (atleast i think it is complex) condition to find competitors from matches schedules and relating to events.
Now I have HTBTM relations with events_competitors table, where multiple events have multiple competitors users entries.
Here, I have used joins condition for joining and getting related events with competitors which works fine, but I also want to apply additional conditions, for is_black (check for black belt) and is_adult (check for adult person)
'EventCompetitor.is_black' => 0,
'EventCompetitor.is_adult' => 0,
Here I want only those competitors which have both conditions (is_black / is_adult) 0, means not eligible, but it does not applying the same, which is resulting in wrong competitors results.
Below is my whole find condition:
$matchdivisions = $this->Competitor->find("all" ,
array(
'conditions' =>
array(
'Competitor.status' => 1,
'Competitor.payment_completed' => 1,
'Competitor.weightgroup_id' => $current_matchsc['Matchschedule']['weightgroup_id'],
'Competitor.rank_id' => $current_matchsc['Matchschedule']['rank_id'],
'Competitor.degree_id' => $current_matchsc['Matchschedule']['degree_id'],
'Competitor.gender' => $current_matchsc['Matchschedule']['gender'],
),
'joins' =>
array(
array(
'table' => 'event_competitors',
'alias' => 'EventCompetitor',
'type' => 'left',
'conditions'=> array(
"AND" =>array(
'EventCompetitor.event_id = '.$current_matchsc['Event']['id'],
'EventCompetitor.is_black' => 0,
'EventCompetitor.is_adult' => 0,
)
),
)
),
'group' => 'Competitor.id'
)
);
Any idea, how can i get those things applied into JOIN conditions, so it is applied into results.
Thanks !
Below is SQL Dump for your ref:
SELECT Competitor.id, Competitor.first_name, Competitor.last_name, Competitor.parent_name, Competitor.gender, Competitor.date_of_birth, Competitor.email_address, Competitor.weight, Competitor.weightgroup_id, Competitor.height, Competitor.rank_id, Competitor.degree_id, Competitor.photo, Competitor.school_id, Competitor.years_of_experience, Competitor.age, Competitor.tournament_id, Competitor.total_registration_fees, Competitor.address1, Competitor.address2, Competitor.city, Competitor.zip_code, Competitor.country_id, Competitor.state_id, Competitor.phone_number, Competitor.mobile_number, Competitor.payment_mode, Competitor.email_sent, Competitor.payment_completed, Competitor.status, Competitor.created, Competitor.modified, Rank.id, Rank.name, Rank.status, Rank.created, Rank.modified, Tournament.id, Tournament.tournament_name, Tournament.tournament_type, Tournament.tournament_date, Tournament.venue_name, Tournament.address1, Tournament.address2, Tournament.city, Tournament.zip_code, Tournament.country_id, Tournament.state_id, Tournament.created, Tournament.modified, Country.id, Country.name, Country.status, Country.created, Country.modified, State.id, State.country_id, State.name, State.short_name, State.status, State.created, State.modified, Degree.id, Degree.rank_id, Degree.name, Degree.status, Degree.created, School.id, School.name, School.address1, School.address2, School.city, School.zip_code, School.country_id, School.state_id, School.phone_number, School.owner_name, School.establishment_date, School.total_competitors, School.status, School.created, School.modified, Transaction.id, Transaction.competitor_id, Transaction.noncompetitor_id, Transaction.created, Transaction.modified, Transaction.mc_gross, Transaction.address_status, Transaction.payer_id, Transaction.address_street, Transaction.payment_date, Transaction.payment_status, Transaction.address_zip, Transaction.first_name, Transaction.address_country_code, Transaction.address_name, Transaction.custom, Transaction.payer_status, Transaction.address_country, Transaction.address_city, Transaction.payer_email, Transaction.verify_sign, Transaction.txn_id, Transaction.payment_type, Transaction.last_name, Transaction.address_state, Transaction.receiver_email, Transaction.item_name, Transaction.mc_currency, Transaction.item_number, Transaction.residence_country, Transaction.transaction_subject, Transaction.payment_gross, Transaction.shipping, Transaction.test_ipn, Transaction.pending_reason FROM competitors AS Competitor left JOIN event_competitors AS EventCompetitor ON (EventCompetitor.event_id = 3 AND EventCompetitor.is_black = 0 AND EventCompetitor.is_adult = 0) LEFT JOIN ranks AS Rank ON (Competitor.rank_id = Rank.id) LEFT JOIN tournaments AS Tournament ON (Competitor.tournament_id = Tournament.id) LEFT JOIN countries AS Country ON (Competitor.country_id = Country.id) LEFT JOIN states AS State ON (Competitor.state_id = State.id) LEFT JOIN degrees AS Degree ON (Competitor.degree_id = Degree.id) LEFT JOIN schools AS School ON (Competitor.school_id = School.id) LEFT JOIN transactions AS Transaction ON (Transaction.competitor_id = Competitor.id) WHERE Competitor.status = 1 AND Competitor.payment_completed = 1 AND Competitor.weightgroup_id = 13 AND Competitor.rank_id = 11 AND Competitor.degree_id = '0' AND Competitor.gender = 'Female' GROUP BY Competitor.id
Here is the left join condition from above query for ref:
left JOIN event_competitors AS EventCompetitor ON (EventCompetitor.event_id = 3 AND EventCompetitor.is_black = 0 AND EventCompetitor.is_adult = 0)

You should be using the containable behavior for this. More at: http://book.cakephp.org/view/1323/Containable
Add it to your Competitor model.
var $actsAs = array('Containable');
Update your model relationships in your Competitor model to include the is_black and is_adult conditions:
var $hasAndBelongsToMany = array(
'Competitor' => array(
'className' => 'Competitor',
'joinTable' => 'event_competitors',
'alias' => 'EventCompetitor',
'conditions' => array(
'EventCompetitor.is_black' => 0,
'EventCompetitor.is_adult' => 0
)
)
);
3) To inject the event id, pass a contain array to your find operation:
$contain = array(
'EventCompetitor' => array(
'conditions' => array('EventCompetitor.event_id' => $current_matchsc['Event']['id'])
)
);
$matchdivisions = $this->Competitor->find("all" ,
array(
'contain' => $contain,
'conditions' => array(
'Competitor.status' => 1,
'Competitor.payment_completed' => 1,
'Competitor.weightgroup_id' => $current_matchsc['Matchschedule']['weightgroup_id'],
'Competitor.rank_id' => $current_matchsc['Matchschedule']['rank_id'],
'Competitor.degree_id' => $current_matchsc['Matchschedule']['degree_id'],
'Competitor.gender' => $current_matchsc['Matchschedule']['gender']
)
)
);
If is_black and is_adult are not always required for the relationship, you would want to move those conditions from the model and pass them in via the contain parameter of the find operation as needed.

Related

Joins not used for complex HABTM search CakePhp 2

I have Contents, which can have Tags belonging to different TagGroups. I have a quite complex search condition which is as follows:
A Content matches if it is tagged with at least one tag from the search as long as it belongs to the same tag group.
Example:
TagGroup 1 are colours, TagGroup2 are shapes.
So if a Content is tagged with "blue", "turquoise" and "rectangular" it will be found, when I search for "blue" and "rectangular"
However this example is only to show that the logic behind this is quite complex.
Content -> ContentsTag <- Tag -> TagGroup
I want to develop a search with paging of the results I had it working, but between refactoring and framework updates it is broken.
At some point I loose the information for the joins and so my SQL is crashing because it is missing tables.
array(
'limit' => (int) 10,
'order' => array(
'Content.objnbr' => 'asc'
),
'joins' => array(
(int) 0 => array(
'table' => 'sang_contents_tags',
'alias' => 'CT1', //join for the first TagGroup
'type' => 'INNER',
'conditions' => array(
(int) 0 => 'CT1.content_id = Content.Id'
)
),
(int) 1 => array(
'table' => 'sang_contents_tags',
'alias' => 'CT2', //join for the second TagGroup
'type' => 'INNER',
'conditions' => array(
(int) 0 => 'CT2.content_id = Content.Id'
)
)
),
'conditions' => array(
'AND' => array(
(int) 0 => array(
'OR' => array(
(int) 0 => array(
'CT1.tag_id' => '189' // chosen Tag 1 from the first TagGroup
)
)
),
(int) 1 => array(
'OR' => array(
(int) 0 => array(
'CT2.tag_id' => '7' // chosen Tag 2 from the second TagGroup
)
)
)
)
),
'contain' => array(
(int) 0 => 'Description',
'ContentsTag' => array(
'Tag' => array(
(int) 0 => 'Taggroup'
)
)
)
)
results in the following SQL:
SELECT `Content`.`id`, `Content`.`objnbr`, `Content`.`name`, `Content`.`imagecounter`, `Content`.`videolength`, `Content`.`money_maker`, `Content`.`comment`
FROM `my_db`.`contents` AS `Content`
WHERE ((`CT1`.`tag_id` = '189') AND (`CT2`.`tag_id` = '7'))
ORDER BY `Content`.`id` DESC
LIMIT 20
So clearly the Tags CT1 and CT2 are not joined and my sql is crashing.
Could it be that the contain is blocking the joins? If I unset the contain I still get the same result / error.
Any ideas?
Edit: To clarify, what I want to achieve:
The result should be a SQL statement like this:
SELECT `Content`.`id`, `Content`.`objnbr`, `Content`.`name`, `Content`.`imagecounter`, `Content`.`videolength`, `Content`.`money_maker`, `Content`.`comment`
FROM
`my_db`.`contents` AS `Content`
INNER JOIN
contents_tags AS CT1 ON CT1.content_id = Content.Id
INNER JOIN
contents_tags AS CT2 ON CT2.content_id = Content.Id
WHERE
((`CT1`.`tag_id` = '189')
AND (`CT2`.`tag_id` = '7'))
ORDER BY `Content`.`id` DESC
LIMIT 10
It looks like the trouble is caused by the pagination. If I do a "simple" find I get Contents based on the Tags:
$result = $this->Content->find('all', $this->paginate['Content']);
generated query by find:
SELECT
`Content`.`id`,
`Content`.`objnbr`,
`Content`.`name`,
`Content`.`imagecounter`,
`Content`.`videolength`,
`Content`.`money_maker`,
`Content`.`comment`
FROM
`my_db`.`contents` AS `Content`
INNER JOIN
`my_db`.`contents_tags` AS `CT0` ON (`CT0`.`content_id` = `Content`.`Id`)
INNER JOIN
`my_db`.`contents_tags` AS `CT2` ON (`CT2`.`content_id` = `Content`.`Id`)
WHERE
((`CT0`.`tag_id` = '56')
AND (`CT2`.`tag_id` = '7'))
ORDER BY `Content`.`objnbr` ASC
I did a research in the bowels of the pagination class and my conclusion is, that it simply is not able to work with the current Paginator, because I cannot pass on my special joins I need for this complex query.
A custom find type also will not help, because my query is too dynamic for that.
Should anybody prove me wrong I will be a happy coder.

Cakephp order by count of join results including not existing entries

my problem is, that i want to count the votes for a comment of an article.
So users can upvote good comments of an article and i want to list the comments with the most votes first. The approach that i'm following now is working, with the limitation, that only votes are listed, that already have been voted. those, that are not listed in the join table (comments_users) are ignored.
to make it a bit more clear my tables are users, comments and the HABTM join table comments_users (alias votes)
my current approach is:
public function commentsOfArticle($articleId){
$options['group'] = array('Comment.articleId');
$options['conditions'][] = array('Comment.article_id' => $articleId);
$options['joins'][] = array('table' => 'comments_users',
'alias' => 'Votes',
'type' => 'inner',
'conditions' => array(
'Votes.comment_id = Comment.id'
));
$options['fields'] = array('Comment.*','COUNT(Votes.user_id) AS votes');
$options['contain'] = array(.......);
$options[ 'order'] = array('votes DESC');
return $this->find('all',$options);
}
i think the key line is
$options['fields'] = array('Comment.*','COUNT(Votes.user_id) AS votes');
is it possible to receive those comments, that have no entry in the votes table at the end of my results, just with votes=0 ?
Try changing the JOIN from inner to left
$options['joins'][] = array('table' => 'comments_users',
'alias' => 'Votes',
'type' => 'LEFT',
'conditions' => array(
'Votes.comment_id = Comment.id'
));

How to remove parent model data by filtering on child model data?

Task
I'm trying to return a set of data based on a condition in the related model.
The problem
Currently the closest I can get is using Containable to return all matching model data, but only returning child data if it matches the contain condition. This isn't ideal as my data still contains the primary model data, rather than it being removed.
I am using a HABTM relationship, between, for example, Product and Category, and I want to find all products in a specific category.
Inital idea
The basic method would be using containable.
$this->Product->find('all', array(
'contain' => array(
'Category' => array(
'conditions' => array(
'Category.id' => $categoryId
)
)
)
));
Although this will return all products, and just remove the Category dimension if it doesn't match the contain condition.
Closest so far
$this->Product->find('all', array(
'contain' => false,
'joins' => array(
array(
'table' => 'categories_products',
'alias' => 'CategoriesProduct',
'type' => 'LEFT',
'conditions' => array(
'CategoriesProduct.product_id' => 'Product.id'
)
),
array(
'table' => 'categories',
'alias' => 'Category',
'type' => 'LEFT',
'conditions' => array(
'Category.id' => 'CategoriesProduct.category_id'
)
)
),
'conditions' => array(
'Product.status_id' => 1,
'Category.id' => $categoryId
),
));
Which generates the following query,
SELECT `Product`.`id`, `Product`.`name`, `Product`.`intro`, `Product`.`content`, `Product`.`price`, `Product`.`image`, `Product`.`image_dir`, `Product`.`icon`, `Product`.`icon_dir`, `Product`.`created`, `Product`.`modified`, `Product`.`status_id`
FROM `skyapps`.`products` AS `Product`
LEFT JOIN `skyapps`.`categories_products` AS `CategoriesProduct` ON (`CategoriesProduct`.`product_id` = 'Product.id')
LEFT JOIN `skyapps`.`categories` AS `Category` ON (`Category`.`id` = 'CategoriesProduct.category_id')
WHERE `Product`.`status_id` = 1
AND `Category`.`id` = 12
This query is correct, except that the join conditions are being quoted ' instead of `, which breaks the query.
Manual query
SELECT *
FROM products
JOIN categories_products ON categories_products.product_id = products.id
JOIN categories ON categories.id = categories_products.category_id
WHERE categories.id = 12
The problem lay in the way I was defining my join conditions. It's not an associative array but rather a string.
'conditions' => array(
'CategoriesProduct.product_id' => 'Product.id'
)
Changes to
'conditions' => array(
'CategoriesProduct.product_id = Product.id'
)

cakephp 'contain' left joint does not use specified foreignKey for left join

On cakephp 2.1, I have two tables: qca belongs to employee via field emp_number on both tables.
qca model belongsTo : (pleae note foreignKey)
public $actsAs = array('Containable');
var $belongsTo = array('Dir',
'Employee' => array(
'className' => 'Employee',
'foreignKey' => 'emp_number')
);
employee model:
public $actsAs = array('Containable');
On my controller's find, i use 'contain' to retrieve employee info based on emp_number from qca table.
$hoursvalues = $this->Qca->find('all', array('conditions' => $conditions,
'fields' => array('Qca.emp_number', 'Sum(CASE WHEN Qca.qca_tipcode = 1 THEN 1 END) AS Qca__comps', 'Sum(qca_end - qca_start) as Qca__production', 'Sum(Qca.qca_durend) as Qca__idle'),
'contain' => array(
'Employee' => array(
'fields' => array('emp_number', 'emp_ape_pat', 'emp_ape_mat', 'emp_ape_mat'))),
'group' => array('Qca.emp_number'),
));
However, the executed sql sentence shows:
LEFT JOIN `devopm`.`employees` AS `Employee` ON (`Qca`.`emp_number` = `Employee`.`id`)
Whereas
Employee.id should be Employee.emp_number
This is the full sql sentence:
SELECT `Qca`.`emp_number`, Sum(CASE WHEN Qca.qca_tipcode = 1 THEN 1 END) AS Qca__comps, Sum(qca_end - qca_start) as Qca__production, Sum(`Qca`.`qca_durend`) as Qca__idle, `Employee`.`emp_number`, `Employee`.`emp_ape_pat`, `Employee`.`emp_ape_mat`, `Employee`.`id` FROM `devopm`.`qcas` AS `Qca` LEFT JOIN `devopm`.`employees` AS `Employee` ON (`Qca`.`emp_number` = `Employee`.`id`) WHERE `Qca`.`dir_id` = 63 AND FROM_UNIXTIME(`Qca`.`qca_start`, '%Y-%m-%d') >= '2012-07-18' AND FROM_UNIXTIME(`Qca`.`qca_start`, '%Y-%m-%d') <= '2012-07-18' GROUP BY `Qca`.`emp_number`
This results on null values returned for Employee:
array(
(int) 0 => array(
'Qca' => array(
'emp_number' => 'id3108',
'comps' => '2',
'production' => '7784',
'idle' => '529'
),
'Employee' => array(
'emp_ape_pat' => null,
'emp_ape_mat' => null,
'id' => null
)
),
Note: I have other instance of 'contain' working (one with default id = tableName.id). I'm wondering if the foreignKey on belongsTo ('foreignKey' => 'emp_number') is just not good for 'contain' to work?
Can you help?
Thank you so much.
(I found a workaround but slows down the query a great deal (it duplicates the left join and takes forever)
$joins = array(
array('table' => 'publication_numerations',
'alias' => 'PublicationNumeration',
'type' => 'LEFT',
'conditions' => array(
'Publication.id = PublicationNumeration.publication_id',
)
)
);
$this->Publication->find('all', array('joins' => $joins));

cakePHP - cant pull model association via belongsTo in second recursion

cakephp I try to get a find('all'...) on a model with many associations in cakePHP 1.3, which does have the filter criteria for the query in the second level of the recursion within the schema. Simply, it looks like this and I want to filter for the UserId:
Delivery belongsTo Order, Order belongsTo User.
Here are the assocs:
Order:
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),....
Delivery:
var $belongsTo = array(
'Order' => array(
'className' => 'Order',
'foreignKey' => 'order_id',
'conditions' => '',
'fields' => '',
'order' => ''
),...
The resulting error is:
SQL Error: 1054: Unknown column 'User.id' in 'where clause' [CORE/cake/libs/model/datasources/dbo_source.php, line 684]
Here the full query, just for fun:
SELECT Delivery.id, Delivery.order_id, Delivery.delivery_address_id, Delivery.deliver_date, Delivery.created, Delivery.modified, Delivery.deliver_run, Delivery.product_mix_id1, Delivery.product_mix_id2, Delivery.product_mix_id3, Delivery.product_mix_id4, Delivery.assembled, Delivery.shipped, Delivery.rated, Delivery.price, Delivery.product_lines_id, Order.id, Order.user_id, Order.product_lines_id, Order.order_date, Order.deliver_monday, Order.deliver_tuesday, Order.deliver_wednessday, Order.deliver_thursday, Order.deliver_friday, Order.deliver_saturday, Order.delivery_address_id, Order.payment_delay, Order.active, Order.cancle_date, Order.replaced_order_id, Order.created, Order.modified, DeliveryAddress.id, DeliveryAddress.delivery_company, DeliveryAddress.delivery_title, DeliveryAddress.delivery_first_name, DeliveryAddress.delivery_last_name, DeliveryAddress.delivery_street, DeliveryAddress.delivery_house_nr, DeliveryAddress.delivery_postal_code, DeliveryAddress.delivery_town, DeliveryAddress.delivery_country, DeliveryAddress.created, DeliveryAddress.deleted, DeliveryAddress.modified, ProductLine.id, ProductLine.name, ProductLine.description, ProductMix1.id, ProductMix1.name, ProductMix1.description, ProductMix1.image_small_path, ProductMix1.image_normal_path, ProductMix1.product_categories_id, ProductMix1.depricated, ProductMix1.created, ProductMix1.modified, ProductMix2.id, ProductMix2.name, ProductMix2.description, ProductMix2.image_small_path, ProductMix2.image_normal_path, ProductMix2.product_categories_id, ProductMix2.depricated, ProductMix2.created, ProductMix2.modified, ProductMix3.id, ProductMix3.name, ProductMix3.description, ProductMix3.image_small_path, ProductMix3.image_normal_path, ProductMix3.product_categories_id, ProductMix3.depricated, ProductMix3.created, ProductMix3.modified, ProductMix4.id, ProductMix4.name, ProductMix4.description, ProductMix4.image_small_path, ProductMix4.image_normal_path, ProductMix4.product_categories_id, ProductMix4.depricated, ProductMix4.created, ProductMix4.modified FROM deliveries AS Delivery LEFT JOIN orders AS Order ON (Delivery.order_id = Order.id) LEFT JOIN delivery_addresses AS DeliveryAddress ON (Delivery.delivery_address_id = DeliveryAddress.id) LEFT JOIN product_lines AS ProductLine ON (Delivery.product_lines_id = ProductLine.id) LEFT JOIN product_mixes AS ProductMix1 ON (Delivery.product_mix_id1 = ProductMix1.id) LEFT JOIN product_mixes AS ProductMix2 ON (Delivery.product_mix_id2 = ProductMix2.id) LEFT JOIN product_mixes AS ProductMix3 ON (Delivery.product_mix_id3 = ProductMix3.id) LEFT JOIN product_mixes AS ProductMix4 ON (Delivery.product_mix_id4 = ProductMix4.id) WHERE User.id = 1
Does anyone know why cake does not pull the second level, in this case the User model, when even recursive is set to 5?
Many thanks.
EDIT: It just occurred to me that in your case you don't need 2nd level JOIN actually, as you can filter by Order.user_id (instead of User.id)! Do you see my point?
So probably you don't need solution below.
As far as I know, Cake never does 2nd level JOIN itself, so for filtering (conditions) on 2nd level (and deeper) I use joins.
For your example:
$options['joins'] = array(
array(
'table' => 'orders',
'alias' => 'Order',
'type' => 'LEFT',
'conditions' => array(
'Order.id = Delivery.order_id',
)
),
array(
'table' => 'users',
'alias' => 'User',
'type' => 'LEFT',
'conditions' => array(
'User.id = Order.user_id',
'User.some_field' => $someFilteringValue
)
)
);
$result = $this->Delivery->find('all', $options);

Resources