Create a query to filter results by multiple data associations with logical disjunction - cakephp

I'm trying to filter results of a table based on two associated tables using a logical disjunction (OR).
For example I want to find all Products filtering on Reviews and Reviews.Users.
where Reviews.Users.super is ->eq(true)
OR
where Reviews.visibility is ->eq('visibility', 'public') AND Reviews.published is ->isNotNull('published')
using two ->innerJoinWith creates a conjunction.
this->Products->find()
->distinct()
->where(['Products.status' => 'current'])
->contain(['Reviews.Users'])
->innerJoinWith('Reviews', function($q) {
return $q
->where(['Reviews.visibility' => 'public'])
->andWhere(['Reviews.published IS NOT' => null]);
})
->innerJoinWith('Reviews.Users', function($q) {
return $q
->where(['Users.super' => true]);
})
->all();
when trying to use leftJoins as suggested by #ndm I get a
$this->Products->find()
->leftJoinWith('Reviews')
->leftJoinWith('Reviews.Users')
->where([
'OR' => [
'Reviews.Users.super' => true,
'Reviews.visibility' => 'public'
]
])
->andWhere([
'Reviews.published IS NOT' => null
])
Column not found: 1054 Unknown column 'Reviews.Users.super' in 'where clause'

Related

How to get contain clause to generate the correct code in CakePHP

I have the following code in a Table definition.
$paginationQuery = $this->find()
->select([
'Members__id','Members__member_type','Members__first_name',
'Members__middle_name','Members__last_name','Members__suffix'
])
->contain([
'SocialMembers' => [
'foreignKey' => false,
'queryBuilder' => function (Query $q) {
return $q->where([
'Members.Members__id' => 'SocialMembers.full_member_id'
]);
}
]
])
->from([
$this->getAlias() => $query
])
->order([
'Members__last_name' => 'ASC',
'Members__first_name' => 'ASC'
]);
return $paginationQuery;
This is to paginate the results of a union of two sets of extracted data.
The problem comes from the queryBuilder function. The left join that is generated looks like this:
LEFT JOIN members SocialMembers ON (
SocialMembers.member_type = 2
AND Members.Members__id = 'SocialMembers.full_member_id'
)
There is an unneeded pair of single quotes around SocialMembers.full_member_id. queryBuilder appears to correctly handle the Members.Members__id, but not the value field of the array. Is there any way to get this to generate correctly?
Was able to resolve this by moving the contains clause to the preceding finder methods.

Select Distinc on cakephp 3 return wrong fields

this function should return the field of the table I want, but this doesn't happen, return all field of the table, with simply sql work fine "SELECT DISTINCT especie FROM packages"
public function listSpicies()
{
$packages = $this->Packages->find('all')
->select('especie')
->distinct('especie');
$this->set([
'success' => true,
'data' => $packages,
'_serialize' => ['success', 'data']
]);
}
I think You can use something like this:
$packages = $this->Packages->find('all' , [
'fields' => [
'anyAlias' => 'DISTINCT(espiece)'
]
])
->toArray();
Notice. If this collection is serialized and outputted as a JSON, check \App\Model\Entity\Package - if espiece is inside $_hidden array - remove this from array

CakePHP3: cannot get matching working properly

If I write the following query:
$sites = $this->Sites->find()
->contain(['Agthemes'])
->matching('Agthemes', function ($q) {
return $q->where([
'Agthemes.site_id IS NOT' => null
]);
})
->all();
I only get Sites which have existing Agthemes.
Now I write a similar query but with one additional association level:
$users = $this->Users->find('all')
->contain([
'Sites.Agthemes'
])
->matching('Sites.Agthemes', function ($q) {
return $q->where([
'Agthemes.site_id IS NOT' => null
]);
})
->distinct(['Users.id'])
->limit(5)
->all();
And in that case, I also get Sites with empty Agthemes.
Could you tell me why?
EDIT
I add the relationships
SitesTable
$this->hasMany('Agthemes', [
'dependent' => true,
'cascadeCallbacks' => true,
]);
$this->belongsToMany('Users', [
'joinTable' => 'sites_users',
]);
UsersTable
$this->belongsToMany('Sites', [
'targetForeignKey' => 'site_id',
'joinTable' => 'sites_users',
]);
AgthemesTable
$this->belongsTo('Sites');
In DebugKit, look at the queries being run. Using 'contain' often runs completely separate queries, then combines the results (depends on the association type).
If you want to be sure to limit the results based on conditions against an associated model, use 'joins' instead of 'contain'.
See this page for details about how the associations use (or don't use) joins, and how you can change the join strategy...etc:
https://book.cakephp.org/3.0/en/orm/associations.html

CakePHP 3 Sort by associated model with HasOne relationship

Order HasOne Suborder
Suborder BelongsTo Order
I need to sort Orders by a field in Suborders, but sorting by virtual fields appears to have been removed in Cake 3.x
In OrdersTable.php, I have
$this->hasOne('Suborder', [
'className' => 'Suborders',
'foreignKey' => 'order_id',
'strategy' => 'select',
'conditions' => function ($exp, $query) {
return $exp->add(['Suborder.id' => $query
->connection()
->newQuery()
->select(['SSO.id'])
->from(['SSO' => 'suborders'])
->where([
'Suborder.order_id = SSO.order_id',
'SSO.suborder_type_id in' => [1, 2, 3]
])
->order(['SSO.id' => 'DESC'])
->limit(1)]);
}
]);
In OrdersController.php, I have
$this->paginate = [
'limit' => 20,
'order' => ['id' => 'desc'],
'sortWhitelist' => [
'id',
'title',
'client_order',
'substatus',
'Workflows.order_status_id',
'Clients.name',
'ProductTypes.type',
'Suborder.due_date',
'Suborder.created',
],
];
$orders = $this->paginate($collection);
In index.ctp, I have
$this->Paginator->sort('Suborder.created', 'Order Placed'),
$this->Paginator->sort('Suborder.due_date'),
and the error I'm getting is Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Suborder.created' in 'order clause'. How do I get Cake to include the Suborder in the initial query for sorting and pagination?
Edit:
$collection = $this->Orders->find()
->contain([
'Clients',
'CurrentAssignment.Users',
'Workflows.OrderStatuses.Category',
'Workflows.OrderStatuses.Departments' => function ($q) use ($uID) {
return $this->Departments->find()->matching('Users', function ($q) use ($uID) {
return $q->where(['Users.id' => $uID]);
});
},
'ClientProducts.ProductTypes',
'Reviews' => function ($q) {
return $q->where(['review_type_id is not' => 6]);
},
'Reviews.ReviewTypes',
'PublicNotes',
'ActiveReview',
'Suborder',
'Suborder.SuborderTypes',
'Suborders.SuborderTypes',
]);
and $collection is modified with 150 lines of wheres, orWheres, and joins based on a number of conditions.
You have configured the assocaition to use the select strategy, which will use a separate query to retrieve the data (currently wrongly documented), hence you cannot reference it in the main query used for pagination.
So you have to use the default join strategy instead if you want to sort on it.
See also
Cookbook > Database Access & ORM > Associations - Linking Tables Together > HasOne Associations

Cakephp 3 : How to add order in queryBuilder for associative data?

Here I have a events field which have a realtion with passwords.I need to order all passwords by download_count DESC. So I have tried below code. It's not giving me any error but desc is not working.
$event = $this->Events->get($id, [
'contain' => [
'EventPasswordAll.Passwords' => [
'queryBuilder' => function (Query $q) {
return $q->order(['download_count' => 'DESC']);
}
]
]
]);
I have also tried
return $q->order(['Passwords.download_count' => 'DESC']);
I get the query
SELECT EventPasswordAll.id AS `EventPasswordAll__id`, EventPasswordAll.event_id AS `EventPasswordAll__event_id`, EventPasswordAll.password_id AS `EventPasswordAll__password_id`, Passwords.id AS `Passwords__id`, Passwords.name AS `Passwords__name`, Passwords.collection_id AS `Passwords__collection_id`, Passwords.photo_count AS `Passwords__photo_count`, Passwords.download_count AS `Passwords__download_count`, Passwords.created AS `Passwords__created`, Passwords.expires_at AS `Passwords__expires_at`, Passwords.recommended AS `Passwords__recommended`, Passwords.accepted AS `Passwords__accepted`, Passwords.user_id AS `Passwords__user_id` FROM event_password_all EventPasswordAll INNER JOIN passwords Passwords ON Passwords.id = (EventPasswordAll.password_id) WHERE EventPasswordAll.event_id in (14)
How to add order in queryBuilder for associative data ?
With an association of EventPasswordAll belongsTo Passwords, the passwords table record will be retrieved via a join, as can be seen in the query. In this case there is no query for Passwords, and thus the query builder is not being invoked.
You have to set the order for the EventPasswordAll query builder instead, like
$event = $this->Events->get($id, [
'contain' => [
'EventPasswordAll' => [
'queryBuilder' => function (Query $q) {
return $q->order(['Passwords.download_count' => 'DESC']);
},
'Passwords'
]
]
]);

Resources