Cakephp - Finding records with no associated records - cakephp

Ok, so I have this:
$user_id = AuthComponent::user('id');
$joins = [
['table' => 'subscriptions',
'alias' => 'Subscription',
'type' => 'INNER',
'conditions' => [
'Subscription.thread_id = Thread.id',
'Subscription.user_id = '.$user_id
]
]
];
$unsubscribed = $this->Thread->find('all',[
'contain' => [
'Subscription' => ['conditions' => ['Subscription.user_id' => $user_id]]
],
'joins' => $joins,
'fields' => ['name','modified'],
'limit' => 10
]);
debug($unsubscribed);
It basically finds all threads with a subscription attached to the current user.
What I really want though, is the negative result. Or in other words: all the threads that do not have a subscription by the current user?
Is there a way to make the conditions negative? Or something like that?

Assuming your relations are:
Thread hasMany Subscription
Subscription belongsTo Thread
Your SQL would be:
SELECT Thread.*
FROM threads AS Thread
LEFT JOIN subscriptions AS Subscription
ON Thread.id = Subscription.thread_id
AND Subscription.user_id = ##user_id##
WHERE Subscription.user_id IS NULL;
The key concept is that you need a LEFT JOIN, not an INNER JOIN. This SQL joins all threads to all of the user's subscriptions AND does not eliminate those thread rows which do not have a subscription (i.e., the purpose of the LEFT JOIN). Then only select records that don't have a subscription (as found in the WHERE clause)
Translating to Cakephp:
$user_id = AuthComponent::user('id');
$joins = [
['table' => 'subscriptions',
'alias' => 'Subscription',
'type' => 'LEFT',
'conditions' => [
'Subscription.thread_id = Thread.id',
'Subscription.user_id = '.$user_id
]
]
];
$unsubscribed = $this->Thread->find('all',[
'conditions' => ['Subscription.user_id' => null],
'joins' => $joins,
'fields' => ['name','modified'],
'limit' => 10
]);
debug($unsubscribed);

Related

Joins with CakePHP 3 Query Builder Not Returning Data

Query is executing just fine but Cakes query builder is not adding the joined fields to the SELECT. What am I missing here? Cake 3.2.10, MySQL, Ubuntu.
$data = $this->Property->find()
->hydrate(false)
->join([
'PublisherProperty' => [
'table' => 'publisher_property', 'type' => 'inner',
'conditions' => "PublisherProperty.property_id = Property.id AND PublisherProperty.publisher_id = " . $this->Publisher->id
],
'PhysicalAddress' => [
'table' => 'property_address', 'type' => 'inner',
'conditions' => "PhysicalAddress.property_id = Property.id AND PhysicalAddress.type = 'physical'"
],
'CheckinAddress' => [
'table' => 'property_address', 'type' => 'left',
'conditions' => "CheckinAddress.property_id = Property.id AND CheckinAddress.type = 'checkin'"
],
'MainTelephone' => [
'table' => 'property_telephone', 'type' => 'inner',
'conditions' => "MainTelephone.property_id = Property.id AND MainTelephone.type = 'main'"
],
'ReservationTelephone' => [
'table' => 'property_telephone', 'type' => 'left',
'conditions' => "ReservationTelephone.property_id = Property.id AND ReservationTelephone.type = 'reservation'"
],
'PropertyDescription' => [
'table' => 'property_description', 'type' => 'left',
'conditions' => "PropertyDescription.property_id = Property.id AND PropertyDescription.publisher_id IN (" . implode(',',$publishers) . ")",
],
])
->where([
'Property.id' => 1111, //$request->property_id,
'Property.status' => 'ready',
])->first();
This is what the Query Builder ends up executing:
SELECT
Property.id AS `Property__id`,
Property.property_type_id AS `Property__property_type_id`,
Property.name AS `Property__name`,
Property.parent_company AS `Property__parent_company`,
Property.short_name AS `Property__short_name`,
Property.url AS `Property__url`,
Property.checkin_time AS `Property__checkin_time`,
Property.checkout_time AS `Property__checkout_time`,
Property.cutoff_days AS `Property__cutoff_days`,
Property.cutoff_time AS `Property__cutoff_time`,
Property.desk_open_time AS `Property__desk_open_time`,
Property.desk_close_time AS `Property__desk_close_time`,
Property.checkin_policy AS `Property__checkin_policy`,
Property.room_tax AS `Property__room_tax`,
Property.commission_rate AS `Property__commission_rate`,
Property.status AS `Property__status`,
Property.tripadvisor_location_id AS `Property__tripadvisor_location_id`,
Property.created AS `Property__created`,
Property.modified AS `Property__modified`
FROM
property Property
inner JOIN publisher_property PublisherProperty ON PublisherProperty.property_id = Property.id
AND PublisherProperty.publisher_id = 2
inner JOIN property_address PhysicalAddress ON PhysicalAddress.property_id = Property.id
AND PhysicalAddress.type = 'physical'
left JOIN property_address CheckinAddress ON CheckinAddress.property_id = Property.id
AND CheckinAddress.type = 'checkin'
inner JOIN property_telephone MainTelephone ON MainTelephone.property_id = Property.id
AND MainTelephone.type = 'main'
left JOIN property_telephone ReservationTelephone ON ReservationTelephone.property_id = Property.id
AND ReservationTelephone.type = 'reservation'
left JOIN property_description PropertyDescription ON PropertyDescription.property_id = Property.id
AND PropertyDescription.publisher_id IN (2, NULL)
WHERE
(
Property.id = 1111
AND Property.status = 'ready'
)
LIMIT
1
Edit: To avoid any "why are you doing it this way" stuff. I am rewriting a legacy application in which the database naming conventions do not fit neatly with cakes naming conventions and the relations are a bit complex. I would use ORM if contain was efficiently querying the database, its not.
Figured this one out go into the Table model and add alias relations so you don't have to custom write queries and can use contains. Example for PhysicalAddress above go into PropertyTable and add the following
$this->hasOne('PhysicalAddress', [
'className' => 'PropertyAddress',
'foreignKey' => 'property_id',
'conditions' => ['PhysicalAddress.type'=>'physical']
]);
Then in your find just do contain('PhysicalAddress')

Cakephp pagination with join table field sort is not working

Cakephp pagination with join table sort is not working for join table fields. But for custom sql join query working fine. Please help me to come out.
See below sample code.. I have Artist.name join table field in order.
$this->paginate = array(
'fields' => array(
'id',
'Song.title',
'Song.date',
'Artist.id AS artist_id',
'Artist.name AS artist_name',
'COUNT(SongViews.id) AS views'
),
'group' => array('ArtistsSong.song_id'),
'recursive' => 0,
'limit' => 20,
'joins' => array(
array(
'table' => 'tbl_artists_songs',
'alias' => 'ArtistsSong',
'conditions'=> array('Song.id = ArtistsSong.song_id')
),array(
'table' => 'tbl_artists',
'alias' => 'Artist',
'conditions'=> array('Artist.id = ArtistsSong.artist_id')
),array(
'table' => 'tbl_song_views',
'alias' => 'SongViews',
'type' => 'left',
'conditions'=> array('SongViews.song_id = ArtistsSong.song_id')
),
),
'order' => array('Artist.name'=>'asc')
);
It is a bug in CakePHP.
However, there is a trick to do it.
You should add a virtual field in your primary model.
Assuming your primary model is Song, you should add this before calling paginate:
$this->Song->virtualFields = array(
'artist_name' => 'Artist.name'
);
And now, you can sort by artist_name.
This question was asked nearly 5 years ago, but I came across the same problem in CakePHP 3. I realised I needed to whitelist the field to allow for sorting:
$this->paginate = array(
...
'sortWhitelist' => array ('Artist.name')
);
The Paginator automatically whitelists fields from the original table but not from the JOINed tables.
$this->paginate = ['fields' => ['id', 'name', 'company_id'],
'contain' => [
'Companies' =>
[
'fields' => ['id', 'name'],
'sort'=>['name'=>'ASC']
]
'limit' => 10,
];
Sorting by columns in associated models requires setting sortWhitelist.
$this->paginate['order'] = [ 'Artist.name' => 'desc' ];
$this->paginate['sortWhitelist'] = ['Artist.name', 'Song.title'];
$this->paginate['limit'] = $this->paginate['maxLimit'] = 200;
In HTML you have to set below line in table header:
<?= $this->Paginator->sort('Song.title', __('Title')) ?>

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

Cakephp $this->paginate with custom JOIN and filtering options

I've been working with cakephp paginations options for 2 days. I need to make a INNER Joins to list a few fields, but I have to deal with search to filter results.
This is portion of code in which I deal with search options by $this->passedArgs
function crediti() {
if(isset($this->passedArgs['Search.cognome'])) {
debug($this->passedArgs);
$this->paginate['conditions'][]['Member.cognome LIKE'] = str_replace('*','%',$this->passedArgs['Search.cognome']);
}
if(isset($this->passedArgs['Search.nome'])) {
$this->paginate['conditions'][]['Member.nome LIKE'] = str_replace('*','%',$this->passedArgs['Search.nome']);
}
and after
$this->paginate = array(
'joins' => array(array('table'=> 'reservations',
'type' => 'INNER',
'alias' => 'Reservation',
'conditions' => array('Reservation.member_id = Member.id','Member.totcrediti > 0' ))),
'limit' => 10);
$this->Member->recursive = -1;
$this->paginate['conditions'][]['Reservation.pagamento_verificato'] = 'SI';
$this->paginate['fields'] = array('DISTINCT Member.id','Member.nome','Member.cognome','Member.totcrediti');
$members = $this->paginate('Member');
$this->set(compact('members'));
INNER JOIN works good, but $this->paginations ignore every $this->paginate['conditions'][] by $this->passedArgs and I cannot have idea how I can work it out.
No query in debug, just the original INNER JOIN.
Can someone helps me ?
Thank you very much
Update:
No luck about it.
I've been dealing with this part of code for many hours.
If I use
if(isset($this->passedArgs['Search.cognome'])) {
$this->paginate['conditions'][]['Member.cognome LIKE'] = str_replace('*','%',$this->passedArgs['Search.cognome']);
}
$this->paginate['conditions'][]['Member.sospeso'] = 'SI';
$this->Member->recursive = 0;
$this->paginate['fields'] = array(
'Member.id','Member.nome','Member.cognome','Member.codice_fiscale','Member.sesso','Member.region_id',
'Member.district_id','Member.city_id','Member.date','Member.sospeso','Region.name','District.name','City.name');
$sospesi = $this->paginate('Member');
everything goes well, and from debug I receive the first condition and the conditions from $this->paginate['conditions'][]['Member.cognome LIKE'], as you can see
array $this->passedArgs
Array
(
[Search.cognome] => aiello
)
Array $this->paginate['conditions'][]
(
[0] => Array
(
[Member.cognome LIKE] => aiello
)
[1] => Array
(
[Member.sospeso] => NO
)
But, if I write the joins with paginate , $this->paginate['conditions'][] will ignore all the stuff, and give me from debug, just $this->paginate['conditions'][]['Reservation.pagamento_verificato'] = 'SI';
Another bit of information.
If I put all the stuff dealing with $this->paginate['conditions'][]['Reservation.pagamento_verificato'] = 'SI';
before the $this->paginate JOIN, nothing will be in $this->paginate['conditions'][].
This is an old question, so I'll just review how to do a JOIN in a paginate for others who got here from Google like I did. Here's the sample code from the Widget's Controller, joining a Widget.user_id FK to a User.id column, only showing the current user (in conditions):
// Limit widgets shown to only those owned by the user.
$this->paginate = array(
'conditions' => array('User.id' => $this->Auth->user('id')),
'joins' => array(
array(
'alias' => 'User',
'table' => 'users',
'type' => 'INNER',
'conditions' => '`User`.`id` = `Widget`.`user_id`'
)
),
'limit' => 20,
'order' => array(
'created' => 'desc'
)
);
$this->set( 'widgets', $this->paginate( $this->Widget ) );
This makes a query similar to:
SELECT widgets.* FROM widgets
INNER JOIN users ON widgets.user_id = users.id
WHERE users.id = {current user id}
And still paginates.
I'm not sure if you need those [] - try just doing this:
$this->paginate['conditions']['Reservation.pagamento_verificato'] = 'SI';
I use the conditions when I call paginate method.
$this->paginate($conditions)
This works ok for me, I hope it works for you!
If you have setted previous params, you may use:
$this->paginate(null,$conditions)
This might be help full to someone....
This is how I did complicated joins with pagination in cakephp.
$parsedConditions['`Assessment`.`showme`'] = 1;
$parsedConditions['`Assessment`.`recruiter_id`'] = $user_id;
$this->paginate = array(
'conditions' => array($parsedConditions ),
'joins' => array(
array(
'alias' => 'UserTest',
'table' => 'user_tests',
'type' => 'LEFT',
'conditions' => '`UserTest`.`user_id` = `Assessment`.`testuser_id`'
),
array(
'alias' => 'RecruiterUser',
'table' => 'users',
'type' => 'LEFT',
'conditions' => '`Assessment`.`recruiter_id` = `RecruiterUser`.`id`'
)
,
array(
'alias' => 'OwnerUser',
'table' => 'users',
'type' => 'LEFT',
'conditions' => '`Assessment`.`owner_id` = `OwnerUser`.`id`'
)
),
'fields' => array('Assessment.id', 'Assessment.recruiter_id', 'Assessment.owner_id', 'Assessment.master_id', 'Assessment.title', 'Assessment.status', 'Assessment.setuptype','Assessment.linkkey', 'Assessment.review', 'Assessment.testuser_email', 'Assessment.counttype_2', 'Assessment.bookedtime', 'Assessment.textqstatus', 'Assessment.overallperc', 'UserTest.user_id', 'UserTest.fname', 'UserTest.lname', 'RecruiterUser.company_name', 'OwnerUser.company_name'),
'limit' => $limit,
'order'=> array('Assessment.endtime' => 'desc')
);

Resources