CakePHP Query - Conditional Based on Contain Field - cakephp

I am developing a system that holds reports from customer's computers and displays failures in a list. I am attempting to write a query that locates all systems that have currently failed or have failures in the past.
My model for Computers has a field that says last_report_pass that allows me to quickly find computers that failed on the current day. My Reports are associated with a computer ID and has a field called status that says whether it was a pass or fail.
I am attempting to write a query that will only show last_report_pass being 0, or failed, or show it if it has reports that were found and joined (meaning there were previous failures).
Here was my idea:
$computers = $this->Computers->find('all', [
'conditions' => [
'last_report_pass' => '0',
'COUNT(Reports) NOT' => '0'
],
'contain' => [
'Reports' => [
'conditions' => [
'status' => '0'
]
]
);
I do not know what to do from here. I could probably write this in SQL but am trying to stick with Cake's ORM Query Builder. Thanks in advance!

You will need to use matching
its similar to contain, but it will filter by associated data:
It will be something like this
$query->distinct(['Computers.id'])
->matching('Reports', function ($q) {
return $q->where(['Reports.last_report_pass' => 0]);
});
Its important to notice that you will also have to contain the Reports table if you need to display some data which is on this table.
Reference

Related

Cannot add subquery to query

I am getting error when I am doing this
$excludedFlows = $this->FlowsPredefined->find('all',[
'conditions' => [
'category' => self::INCREASE_CUSTOMER_LOYALTY_FLOWS_CATEGORY,
'is_new' => 1,
'channel' => 'all'
]
])
->select('id')
->all();
$conditions['FlowsPredefined.id NOT IN'] = $excludedFlows;
The error is :
Impossible to generate condition with empty list of values for field
(FlowsPredefined.id)
The excludedFlows variable is not empty. I have 6 results in it. How to execute NOT IN in cakePHP ?
You're not passing a query, but a result set, as you're calling all(), and result sets are not supported as condition values.
Either do not call all() if you want to actually use a subquery, or, if applicable (you don't want to do this with huge numbers or results), extract the IDs into an array, like ->all()->extract('id')->toList().

CakePHP 3 belongsToMany conditions

I have a problem with declaring conditions for my BTM association - probably missunderstood something.
Imagine a few tables - Notes, NotesEntities, Entities (the last one is not an actual table, but can be any table like Products, Customers, Services, etc...)
In NotesTable there is "entity" field in which there are values like "Services", "Products", etc...
I have se an association in NotesTable like this assuming it will get rows from ServicesTable only if "entity" field in NotesTable = "Services":
$this->belongsToMany('Services', [
'foreignKey' => 'note_id',
'targetForeignKey' => 'entity_id',
'joinTable' => 'notes_entities',
'conditions' => ['Notes.entity' => 'Services']
]);
But the condition doesn't work and so if I want to fetch a note, where entity = "Customers", it fetches also Services where id of a service = wanted id of a customer. For example: I fetch a Note which has entity = "Customers" and is connected to Customers with ids [1, 2]. That works fine. But when I contain Services, it also fetches Services with ids [1, 2] instead of leaving "services" array empty unless entity = "Services".
Is there a way to achieve such association? Is it possible to set such global conditions in CakePHP 3? I believe I'm not the only one who can use such thing. What am I missing?
EDIT 1:
The other way around it works. If I fetch a customer and contain Notes, everything is fine and it fetches only notes with entity = "Customers".

CakePHP 3 hasMany association with bitwise expression

I have two tables, AclGroups and AclPermissions, and I want to create a hasMany relationship between them, i.e AclGroups has many AclPermissions.
The condition to determine whether a group owns a given permission is done in a single bitwise check. This is what i'm trying to do:
SELECT
*
FROM
acl_groups
JOIN
acl_permissions ON acl_permissions.permission & acl_groups.permission != 0
In AclGroupsTable I have tried the following:
$this->hasMany('AclPermissions', [
'className' => 'AclPermissions',
'foreignKey' => 'permission',
'conditions' => [
'AclPermissions.permission & AclGroups.permission !=' => 0
]
]);
But that just gives me
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'aclgroups.permission' in 'where clause'
In my controller I do:
$this->AclGroups->find('all')->contain(['AclPermissions']);
I suppose the real question is: Is there a way I can change the conditions of the ON clause in the query that fetches associated records
As mentioned in the comments, records of hasMany associations (and belongsToMany for that matter) will always be retrieved in a separate query when using contain().
If you need to create joins with such an association, then you must explicitly use the corresponding functionality for joins, for example leftJoinWith():
$this->AclGroups->find('all')->leftJoinWith('AclPermissions');
This will create a query similar to the one you are showing. However it would also generate the default conditions using the configured foreign key, you'd have to disable the foreign key in order to avoid that, like:
$this->hasMany('AclPermissions', [
'foreignKey' => false, // << like this
// ...
]);
Given that the association conditions won't work with contain() (and disabling the foreign key will make it even more unusable for that purpose), you may want to create a separate association for your joining purposes, or use the "lower level" join methods, where you specify all the conditions manually (you can for example put this in a custom finder in order to keep your code DRY):
$query = $this->AclGroups
->find('all')
->join([
'table' => 'acl_permissions',
'alias' => 'AclPermissions',
'type' => 'LEFT',
'conditions' => [
'AclPermissions.permission & AclGroups.permission != :permission'
]
])
->bind(':permission', 0, 'integer');
Note that the value is being explicitly bound here to ensure that the correct type is being used, as it couldn't be determined from the non-standard left hand value (which isn't really ment to contain SQL snippets - you may want want to look into using expressions).
See also
Cookbook > Database Access & ORM > Query Builder > Using leftJoinWith
Cookbook > Database Access & ORM > Query Builder > Adding Joins
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods

CakePHP 3 - access params of Query object

In CakePHP 3.x I can do this:
$Substances = TableRegistry::get('Substances');
$query = $Substances->find()->where($where_conditions)->select(['id']);
debug($query);
This will show me the Query object.
If I want to get the SQL string I can use debug($query->sql());. This will give me the SQL string with placeholders for any parameters, e.g.
SELECT ... WHERE ... in (:c0,:c1,:c2))
When using debug($query) I can see the values for :c0, :c1, etc:
'params' => [
':c0' => [
'value' => (int) 47,
'type' => 'smallinteger',
'placeholder' => 'c0'
],
':c1' => [
'value' => (int) 93,
'type' => 'smallinteger',
'placeholder' => 'c1'
],
':c2' => [
'value' => (int) 845,
'type' => 'smallinteger',
'placeholder' => 'c2'
],
':c3' => [
'value' => (int) 354,
'type' => 'smallinteger',
'placeholder' => 'c3'
]
]
However, I cannot access them outside the debug statement. For example $query->params() or $query['params'] doesn't give me the parameters - it will error. I want to be able to pass this array to a custom function, so how can I access it?
It's strange because I can use debug($query->sql()) to get the SQL string as above, and params is just another thing in that object, but doesn't seem to be accessible.
I've read How to get params from query object in CakePHP 3 but think that's a different question as it was to do with not seeing the values in the debug statement due to the default depth that debug would provide.
The reason I want to do this is because I want to be able to do a CREATE TABLE AS query that will write the values of the SELECT statement into a new table (Important: see this link for an example of how that works in vanilla MySQL). I can't figure out how to do that with the ORM in Cake, so was planning on writing a custom function. But I need to be able to access both the SQL as well as the parameters bound so that the query can be executed in my own function.
If you know of a solution where I can use the ORM to do the CREATE TABLE AS query, I'm still interested to know about this. However I would like to know if params are accessible outside debug() as well.
Premise: I did not actually understand why you need the params
anyway. The information you need is stored by the query ValueBinder object
so you could simply do
$params = $query->getValueBinder()->bindings();
debug($params);
but for some reason this will get you an empty array. My guess is that the query need some kind of initialization first.
in fact if you run
debug($query);
$params = $query->getValueBinder()->bindings();
debug($params);
you'll see your params. I think someone more expert than me will come and give a full explanation
edit: I noticed that debugging $query calls $query->sql() which in turns calls conection->compileQuery();
so you can do
$query->sql(); // you need this to compile the query
// and generate the bindings
$params = $query->getValueBinder()->bindings();
CakePHP does not provide specific methods for creating such CREATE TABLE AS statements, so you'll have to build that on your own.
Compiling a query as the one shown in your question is simple enough using the query objects sql() method, and as arilia already mentioned, you'll be able to access the parameters bound to that query after is was compiled.
Having the compiled SQL and the associated value binder, you can combine this with a custom raw query to build your CREATE TABLE AS statement. All you need to do is prepare a new statement with the compiled SQL, and attach the value binder via its own attachTo() method.
One thing you might also have to do, is to define custom aliases in your select(), as otherwise you'd end up with columns selected (and created) in the form of Substances_id.
$Substances = TableRegistry::get('Substances');
$selectQuery = $Substances
->find()
->where($where_conditions)
->select(['id' => 'id']); // < create aliases as required
// compile the ORM query, this will populate the value binder
$selectSql = $selectQuery->sql();
// combine table creation SQL and compiled ORM query in a single statement
$createStatement = $Substances
->getConnection()
->prepare('CREATE TABLE dynamic_table AS ' . $selectSql);
// attach the ORM querys value binder, binding all its values to the given statement
$selectQuery->getValueBinder()->attachTo($createStatement);
$success = $createStatement->execute();
This should create SQL similar to:
CREATE TABLE dynamic_table AS
SELECT
id AS id
FROM
substances Substances
WHERE
...
See also
Cookbook > Database Access & ORM > Database Basics > Interacting with Statements
API > \Cake\ORM\Association::attachTo()

cakephp habtm join issue

I have table structure like below:
events (boxing, sparring etc)
competitors (users who are participating into diff events)
events_competitors (every competitor's selected events will go here)
Now what i want is, as per my match schedules i want to find competitors from schedules tables, where each competitors will be matching with events they have selected at registration time.
I'm using a find query something like this:
$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('EventCompetitor.event_id = '.$current_matchsc['Event']['id']),
)
)
)
);
Here, I have used joins because I am not able to find relations from EventCompetitor matching with Competitors and Events.
Problem: The single matching record comes 10 times, because of join while it should be single time only.
Earliest reply would be appreciated.
Thanks in advance!
Now a next level once this worked for me:
I have two levels of conditions check, in EventsCompetitors I want to see, if they want to have a fight with black belt or if they are minor and they want to have fight with adults so in this case, I want to see these both flags and on that basis, again my conditions will be changed and a new results will be displayed for my additional displays.
Kindly let me know, if sometime anyone have idea on this kind of stuffs with CakePHP.
Again a lot thanks to stackoverflow for their excellent services !
If you are only interested in a single record and are always sure that the others are duplicates then you can use find('first', ...) instead.
If you are interested in multiple different records but you are getting multiples of each of those records you could add in a 'group' => array('') to aggregate the competitors on a specific field to only return them once ?

Resources