CakePHP 3.7. Trying to use the ORM to write a query which contains a MySQL COALESCE condition.
Followed advice on CakePHP 3 - How to write COALESCE(...) in query builder? and ended up having to write it manually using newExpr() as this was the given solution.
The code I have is as follows:
$TblRegulatoryAlerts = TableRegistry::getTableLocator()->get('TblRegulatoryAlerts');
$subscribed_to = $TblRegulatoryAlerts->getUserRegulations($u_id, $o_id, false);
$query = $this->find()
->contain('Filters.Groups.Regulations')
->select(['id', 'date', 'comment', 'Filters.label', 'Filters.anchor', 'Groups.label']);
$query->select($query->newExpr('COALESCE((SELECT COUNT(*)
FROM revision_filters_substances
WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances'));
$query->where(['date >=' => $date_start, 'date <=' => $date_end, 'Regulations.id' => $regulation_id, 'Filters.id IN' => $subscribed_to]);
$query->enableHydration(false)->orderDesc('date');
This produces the following SQL (output of debug($query->sql()):
SELECT RevisionFilters.id AS `RevisionFilters__id`, RevisionFilters.date AS `RevisionFilters__date`, RevisionFilters.comment AS `RevisionFilters__comment`, Filters.label AS `Filters__label`, Filters.anchor AS `Filters__anchor`, Groups.label AS `Groups__label`, (COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances) FROM revision_filters RevisionFilters INNER JOIN dev_hub_subdb.filters Filters ON Filters.id = (RevisionFilters.filter_id) INNER JOIN dev_hub_subdb.groups Groups ON Groups.id = (Filters.group_id) INNER JOIN dev_hub_subdb.regulations Regulations ON Regulations.id = (Groups.regulation_id) WHERE ...
Unfortunately this doesn't execute because Cake is putting in un-necessary parentheses surrounding the COALESCE statement, which changes the SQL.
In the above code it generates:
(COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances)
Whereas it needs to omit the parentheses surrounding COALESCE so it's just:
COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances
Is this possible?
Don't specify the alias in the expression, instead specify it using the key => value syntax of the Query::select() method, like this:
$query->select([
'count_substances' => $query->newExpr('...')
]);
It would still wrap the expression in parentheses, but that would then be valid as it doesn't include the alias.
That being said, using the function builders coalesque() method should work fine, the problem described in the linked question can be fixed by using the key => value syntax too, where the value can specify the kind of the argument, like ['Nodes.value' => 'identifier'], without that it would bind the value as a string.
However there shouldn't be any such problem with your example, using the function builders coalesce() method should work fine.
$query->select([
'count_substances' => $query->func()->coalesce($countSubquery, 1, ['integer'])
]);
The type argument is kinda optional, it would work with most DBMS without it, but for maximum compatibility it should be specified so that the integer value is being bound properly, also it will automatically set the return type of the function (the casting type) to integer too.
Related
I know this same question is already asked before. But I have tried the solution but it's not working for me.
$comp_ids = AllowArea::find()
->select(['comp_code'])
->where(['user_id' => Yii::$app->user->id])
->column();
$ref = (new \yii\db\Query())
->select([
'ProductCode',
'ProductNameFull',
'ProductSpec',
'ProductGroup',
'CompanyCode',
'CompanyName'
,'Price',
'PurchasePrice'
])->from('Product')
->andFilterWhere(['CompanyCode' => $comp_ids])
->all(Yii::$app->sds);
It's giving me empty data.
Flow
The users are assigned areas and some users are assigned areas with a company. So I want the above query to return me the result whether the condition fails or not.
Update 1
The SQL which I am getting is
SELECT `ProductCode`, `ProductNameFull`, `ProductSpec`, `ProductGroup`,
`CompanyCode`, `CompanyName`,
`Price`, `PurchasePrice` FROM `Product` WHERE `CompanyCode` IS NULL
Any help would be highly appreciated.
If nothing works, from what you were told, then you can try:
$ref = (new \yii\db\Query())
->select([
'ProductCode',
'ProductNameFull',
'ProductSpec',
'ProductGroup',
'CompanyCode',
'CompanyName'
,'Price',
'PurchasePrice'
])->from('Product');
if (!empty($comp_ids)) {
$ref->andFilterWhere(['CompanyCode' => $comp_ids]);
}
$ref = $ref->all(Yii::$app->sds);
Try user orFilterWhere() this Adds an additional WHERE condition to the existing one but ignores empty operands.
The new condition and the existing one will be joined using the 'OR' operator.
This method is similar to orWhere(). The main difference is that this method will remove empty query operands. As a result, this method is best suited for building query conditions based on filter values entered by users.
I am trying to add OR SQL Query Statement to my CakePHP project, but notice I am getting AND instead of OR.
I am getting this string when I use SQL(), and no OR is include whereas OR is expected
SELECT Blacklist.id AS `Blacklist__id`,
Blacklist.provider AS `Blacklist__provider`,
Blacklist.receiver AS `Blacklist__receiver`,
Blacklist.platform AS `Blacklist__platform`,
Blacklist.brokers AS `Blacklist__brokers`
FROM blacklist Blacklist
WHERE (brokers = :c0 AND provider = :c1 AND platform=FXCMMetaTrader4)
I tried the code below, to but instead of OR I am getting AND.
Why is this working this way and how can I avoid this?
//fill blacklist data
$blacklistQuery = $blacklistModel->find()
->select(['id', 'provider', 'receiver', 'platform', 'brokers'])
->order(array('created' => 'desc'))
->where(function (QueryExp $exp, Query $q) use ($accountBroker, $accountLogin,$platform) {
$orBrokerCond = $exp->or_(['brokers' => $accountBroker]);
$orProviderCond = $exp->or_(['provider' => $accountLogin]);
$orPlatformCond = $exp->or_('platform='. $platform);
return $exp->add($orBrokerCond)
->add($orProviderCond)
->add($orPlatformCond);
});
// ->where("provider = $accountLogin");
echo $blacklistQuery->sql();
Inner expression objects usually do not affect outer expression objects, ie the main expression doesn't really care about what type of expression you are passing to it.
What you are doing there is creating 3 separate expression objects, each containing only a single condition. Each of those objects will compile itself to a single conditional statement, and those statements will then be concatenated using AND as you are passing them to the main expression that uses AND by default.
What you are trying to create could simply be expressed as:
return $exp->or_([
'brokers' => $accountBroker,
'provider' => $accountLogin,
'platform' => $platform,
]);
If you where to add OR conditions to another expression, then you'd pass such an object with multiple conditions, ie:
$or = $exp->or_([
'brokers' => $accountBroker,
'provider' => $accountLogin,
'platform' => $platform,
]);
return $exp->add($or);
ps, do NOT do stuff like 'platform='. $platform, that's a possible SQL injection vulnerability!
See also
Cookbook > Database Access & ORM > Query Builder > Advanced Conditions
I am trying to learn CakePHP 3, but I have run into a problem:
I have two tables languages and rich_text_elements, and want to join them in the following manner:
$all = $this->find()->
select(['i18n','Language.long_name'])->
innerJoin(['Language' => 'languages'], ['Language.i18n' => 'RichTextElements.i18n'])->
group('RichTextElements.i18n')->
order(['RichTextElements.i18n'])->all();
The following query is produced:
SELECT RichTextElements.i18n AS `RichTextElements__i18n`,
Language.long_name AS `Language__long_name`
FROM rich_text_elements RichTextElements
INNER JOIN languages Language ON Language.i18n = :c0
GROUP BY RichTextElements.i18n ORDER BY RichTextElements.i18n;
If I replace ":c0" with "RichTextElements.i18n", this query runs fine alone (in HeidiSql) and returns five rows of data, exactly as I expect it to.
But CakePHP returns an empty set!
The problem seem related to the innerJoin() because if I modify the query to select only from the RichTextElements table, it will return five rows as expected, in CakePHP:
Runs fine:
$all = $this->find()->
select(['i18n'])->
group('RichTextElements.i18n')->
order(['RichTextElements.i18n'])->all();
Anyone see what I don't see?
As stated in the API:
Conditions can be expressed [...] using a string for comparing columns, or string with already quoted literal values. Additionally it is possible to use conditions expressed in arrays or expression objects.
Taken from Query::join() | Using conditions and types.
Try the following:
$all = $this->find()
->select(['i18n','Language.long_name'])
->innerJoin(
['Language' => 'languages'],
['Language.i18n' => new \Cake\Database\Expression\IdentifierExpression('RichTextElements.i18n')])
->group('RichTextElements.i18n')
->order(['RichTextElements.i18n'])->all();
This should also work:
$all = $this->find()
->select(['i18n','Language.long_name'])
->innerJoin(
['Language' => 'languages'],
['Language.i18n = RichTextElements.i18n'])
->group('RichTextElements.i18n')
->order(['RichTextElements.i18n'])->all();
I have a collection in my Mongo Database called WorkOrder with 2 fields DateComplete and DateDue. Using those 2 fields I'd like to use the aggregation framework to count the number of 'Late' Work Orders by comparing the two fields. However the research I've found hasn't had any useful ways to format the query so that the 'Late' Work Orders will be filtered through. Does anyone know of a way to format a Mongo DB Aggregation Query (preferably in PHP) that can compare 2 fields in the collection?
EDIT:
An example entry in WorkOrder might look like
_id
some mongo id
DateDue
2014-10-10
DateCompleted
2014-10-12
This entry would want to be filtered through since DateCompleted is greater than DateDue. I didn't know about the $cond operator so I haven't tried anything for that yet.
EDIT:
After trying #BatScream's suggestion with the following query in my PHP script
array(
'$cond' => array(
'if' => array(
'dateDue' => array(
'$lt' => 'dateComplete
)
)
)
)
However the MongoCollection::Aggregate function told me that $cond wasn't a recognized operator.
EDIT: #BatScream's answer seems to work but I wasn't aware of the fact that the group operator doesn't work properly after a $project is applied. I was hoping to be able to group these document on another field cID, is that possible?
The below aggregation pipeline would give you the result, considering your fields are of ISODate type. If not i suggest you to store them as ISODate type and not Strings.
db.collection.aggregate([
{$project:{"isLateWorkOrder":{$cond:[{$lt:["$dateDue","$dateCompleted"]},
true,false]}}},
{$match:{"isLateWorkOrder":true}},
{$group:{"_id":null,"lateWorkOrders":{$sum:1}}},
{$project:{"_id":0,"lateWorkOrders":1}}
])
The PHP syntax should look similar to,
$projA = array("isLateWorkOrder" =>
array("$cond" =>
array(array("$lt" =>
array("$dateDue","$dateCompleted")),
true,false)))
$matchA = array("isLateWorkOrder" => true)
$grp = array("_id" => null,"lateWorkOrders" => array("$sum" => 1))
$projB = array("_id" => 0,"lateWorkOrders" => 1)
$pipeline = array($projA,$matchA,$grp,$projB);
$someCol -> aggregate($pipeline)
or, simply using the count function:
db.collection.count({$where:"this.dateDue < this.dateCompleted"})
Using Medoo PHP database framework and trying to make a join
$users = $db->select(
'users',
[
'[>] tournaments_users' =>
[
'tournaments_users.user_id' => 'users.uid'
]
], [
'users.uid',
'users.name',
'users.modifier',
'users.handicap',
'tournaments_users.tournament_id'
], [
'tournaments_users.tournament_id' => 1
'ORDER' => 'users.username ASC'
]
);
foreach( $users as $u) {
echo $u['name'].'<br>';
}
The selection results in an invalid argument supplied for foreach().
Removing 'tournaments_users.tournament_id' from the column- and where-section makes the query work, but does not show the correct data.
Why is the query invalid?
Changing the join-selection to
'[>]tournaments_users' =>
[
'uid' => 'user_id'
]
solved the issue with invalid argument.
Concatenation of logical expressions using AND/OR in join clause is currently not supported by the medoo library (medoo 0.9.6.2). You can only use the query($query) method given by medoo to directly execute sql queries. For your example the query looks like this:
$data = $db->query("SELECT u.user_id, s.shirt_id FROM `cvs_users` u
LEFT JOIN `shirts` s
ON user_id = shirt_owner_id AND shirt_being_worn = 1
WHERE user_id = 1")->fetchAll();
Note the call to fetchAll() at the end to get the queried data. I ran into the same problem and debugging the medoo.php code revealed that AND/OR conditions are only considered within where clause. Maybe they'll put this feature in a future update.