Cannot add subquery to query - cakephp

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().

Related

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 Query - Conditional Based on Contain Field

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

In CakePHP 3.X using addCase how should I define a value as a fallback? [duplicate]

I'm trying to get a query working using a case statement, and can't figure out how to get the case to return a column value instead of a constant. I have the query working perfectly, except that the column names I'm providing for the results are being quoted or otherwise mishandled by Cake or maybe PDO somewhere down in a layer that I can't dig my way through. I got as far down as bindValue, but none of the documentation I encountered along the way tells me how to do this.
I have found this example comment:
$statement->bindValue(1, 'a title');
$statement->bindValue(2, 5, PDO::INT);
$statement->bindValue('active', true, 'boolean');
$statement->bindValue(5, new \DateTime(), 'date');
but in all these cases, the value provided is a constant. I need to pass in a string that is the name of the column that I want returned.
I tried both 'string' (resulted in quoted column name) and 'integer' (resulted in 0). I tried PDO::FETCH_COLUMN (seemed highly unlikely, but looked like the next best bet from http://php.net/manual/en/pdo.constants.php, and easy to try it...). I tried 'literal', inspired by the way you can put literal strings into expressions (resulted in Error: unknown type "literal"). That error message led me to src/Database/Type.php, but nothing in there helped me either.
So, I'm pretty much stumped. Here's a simple version of the code I have (leaving out a couple of conditions and unrelated columns):
$query = $this->Games->find();
$team_id = $query->newExpr()->addCase(
[$query->newExpr()->eq('Games.status', 'home_default')],
['home_team_id', 'away_team_id'],
['string', 'string']
);
$defaulting = $query
->select([
'id' => $team_id,
'count' => 'COUNT(Games.id)',
])
->where([
'Games.status IN' => ['home_default', 'away_default'],
])
->group('id')
->toArray();
This generates this SQL:
SELECT
(CASE WHEN Games.status = 'home_default'
THEN 'home_team_id' ELSE 'away_team_id' END) AS `id`,
COUNT(Games.id) AS `count`
FROM games Games
WHERE Games.status in ('home_default','away_default')
GROUP BY id
Note that THEN 'home_team_id' ELSE 'away_team_id' END should be simply THEN home_team_id ELSE away_team_id END. This will then allow me to read the list of ids of teams that have defaulted games along with the number of games they defaulted.
By default the values passed to the second argument of QueryExpression::addCase() are being treated as to be converted to literal values, not as identifiers. If you need the latter, then you should use an expression, an IdentifierExpression.
use Cake\Database\Expression\IdentifierExpression;
// ...
$team_id = $query->newExpr()->addCase(
[
$query->newExpr()->eq('Games.status', 'home_default')
],
[
new IdentifierExpression('Games.home_team_id'),
new IdentifierExpression('Games.away_team_id')
]
);
Also ditch the third argument in this case, you don't want the values to be string literals (for expressions the types would be ignored anyways).

different output for model name with capital letter in cakephp3

I dont understand how I get a different output for lesson_date field if in the select I use Lessons.lesson_date' or lessons.lesson_date. I thought I am supposed to use Lessons and not lessons and that the for a single name it doesnt really matter.
with Lessons.lesson_date I get on the debug :
'lesson_date' => object(Cake\I18n\FrozenDate) {
'time' => '2015-07-09T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
with lessons.lesson_date I get a better output:
'lessons' => [
'id' => '5399',
'lesson_date' => '2015-07-09'
//this is the code below I am talking about. The Lessons.lesson_date gives a different output than if I change this to lessons.lesson_date
$query3 = $this->Lessons->find()
->contain(['TutoringTypes'])
->select(['lessons.id','Lessons.lesson_date','Lessons.tutoring_type_id',
'TutoringTypes.value'])
->where(['Lessons.lesson_date >' => $a3,'Lessons.lesson_date <' => $a4, .....
That is the expected/correct behavior when following the conventions, the date gets casted according to its type.
The latter output might be better suited for what you are doing with the data, but generally the former is "better" since a date object gives you more freedom of manipulating dates, handling localization, output formatting, etc...
Why?
As to the why, the output is different because the ORM will not cast the value in case of a non-conventional column alias, as it's not present in the type map that holds the information about which column is of which type.
Using lessons.lesson_date will create an alias of
lessons__lesson_date
which is not following the conventions, where as using Lessons.lesson_date will create an alias of
Lessons__lesson_date
which does follow the conventions, and will match the field in the type map, causing the ORM to cast the data.
Changing the behavior
If you need YYYY-MM-DD output, then you could simply output it formatted it in your view like
echo $lesson->lesson_date->i18nFormat('yyyy-MM-dd')
or change the default output format (which is used when the date gets casted to a string)
\Cake\I18n\FrozenDate::setToStringFormat('yyyy-MM-dd');
or maybe even change the type in the type map in order to keep the date string as is
$query = $this->Lessons
->find()
// ...
$types = ['Lessons__lesson_date' => 'string'] + $query->selectTypeMap()->defaults();
$query->selectTypeMap()->defaults($types);
See also
Cookbook > Date & Time > Formatting
Cookbook > Date & Time > Setting the Default Locale and Format String
\Cake\ORM\Query::_addDefaultSelectTypes()
\Cake\Database\Query::selectTypeMap()
\Cake\Database\Query::typeMap()

CakePHP 1.3 Return all fields but with one of the fields as DISTINCT

What command would I use to to do a 'find' which would return all fields with the proviso that one of the fields is DISTINCT, without listing all the fields in the 'fields' array?
One can do this:
$this->Car->find('all', array('fields' => array(DISTINCT Car.colour)));
but that just returns the 'colour' field.
I want to do something like:
$this->Car->find('all', array('fields' => array('*', DISTINCT Car.colour)));
The only way to return all the fields is to list them all in the 'fields' array but I want to avoid this.
$this->Car->find('first', array('conditions'=>array('Car.colour'=>'blue')));
This will return the result that you trying to get. I'm afraid this is not what you expected :)

Resources