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"})
Related
In my MongoDB collection, all documents contain a mileage field which currently is a string. Using PHP, I'd like to add a second field which contains the same content, but as an integer value. Questions like How to change the type of a field? contain custom MongoDB code which I don't want to run using PHP, and questions like mongodb php Strings to float values retrieve all documents and loop over them.
Is there any way to use \MongoDB\Operation\UpdateMany for this, as this would put all the work to the database level? I've already tried this for static values (like: add the same string to all documents), but struggle with getting the data to be inserted from the collection itself.
Some further hints:
I'm looking for a pure PHP solution that does not rely on any binary to be called using exec. This should avoid installing more packages than needed on the PHP server
Currently, I have to use MongoDB in v4.0. Yes, that's not the most recent version, but I'm not in the position to perform an upgrade
Try this, please:
01) MongoDB Aggregate reference:
db.collectionName.aggregate(
[
{ "$addFields": {
"intField": { "$toInt": "$stringFieldName" }
}},
{ "$out": "collectionName" }
]
)
02) Possible PHP solution (Using as reference https://www.php.net/manual/en/mongocollection.aggregate.php):
$pipeline = array(
array(
'$addFields' => array(
'integerField' => array('$toInt' => '$mileage')
)
),
array(
'$out' => 'collection'
),
);
$updateResult = $collection->aggregate(pipeline);
You could use $set like this in 4.2 which supports aggregation pipeline in update.
$set stage creates a mileageasint based on the previous with $toInt value
db.collection.updateMany(
<query>,
[{ $set: { "mileageasint":{"$toInt":"$mileage" }}}],
...
)
Php Solution ( Using example from here)
$updateResult = $collection->updateMany(
[],
[['$set' => [ 'mileageasint' => [ '$toInt' => '$mileage']]]]
);
I have two tables. First table contain offer details and second table contains offer collection of languages(collection can be selected in main offer form). I want to use query with "join" language table to get all of them in single query. How it's look like:
Relation
Trying to get all of the offers I want to get all offer languages as one field.
array [
'id' => string '11',
'name' => string '134',
'date' => string '01-12-2016',
'languages' => array(all language value from related table)
]
Here is my query:
$select = $this->getDbTable()->select()
->setIntegrityCheck(false)
->from(array('o' => 'offers'), array('o.*'))
->joinLeft(array('ol' => 'offer_language'), 'ol.id_offer = o.id', array('ol.*'));
$resultSet = $this->getDbTable()->fetchAll($select)
In that way if one offer has a three language then I get three same offer with different language field.
Zend Version: 1.11
Thanks for help.
The solution is to use a Zend_Db_Expr with GROUP_CONCAT.
Here are two posts that will give you a clear explanation:
Can I concatenate multiple MySQL rows into one field? and How to use GROUP_CONCAT with Zend Framework?
Your code will end up looking something like this:
$select = $this->getDbTable()->select()
->setIntegrityCheck(false)
->from(array('o' => 'offers'), array('o.*'))
->joinLeft(array('ol' => 'offer_language'), 'ol.id_offer = o.id', array('languages' => new Zend_Db_Expr('GROUP_CONCAT(ol.language)')))
->group('o.id');
Good luck!
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();
We are trying to search a dynamodb, and need to get count of objects within a grouping, how can this be done?
I have tried this, but when adding the second number, this doesn't work:
$search = array(
'TableName' => 'dev_adsite_rating',
'Select' => 'COUNT',
'KeyConditions' => array(
'ad_id' => array(
'ComparisonOperator' => 'EQ',
'AttributeValueList' => array(
array('N' => 1039722, 'N' => 1480)
)
)
)
);
$response = $client->query($search);
The sql version would look something like this:
select ad_id, count(*)
from dev_adsite_rating
where ad_id in(1039722, 1480)
group by ad_id;
So, is there a way for us to achieve this? I can not find anything on it.
Trying to perform a query like this on DynamoDB is slightly trickier than in an SQL world. To perform something like this, you'll need to consider a few things
EQ ONLY Hash Key: To perform this kind of query, you'll need to make two queries (i.e. ad_id EQ 1039722 / ad_id EQ 1480)
Paginate through query: Because dynamodb returns your result set in increments, you'll need to paginate through your results. Learn more here.
Running "Count": You can take the "Count" property from the response and add it to the running total as you're paginating through the results of both queries. Query API
You could add a Lambda function triggered by the DynamoDBStream, to aggregate your data on the fly, in your case add +1 to the relevant counters. Your search function would then simply retrieve the aggregated data directly.
Example: if you have a weekly online voting system where you need to store each vote (also to check that no user votes twice), you could aggregate the votes on the fly using something like this:
export const handler: DynamoDBStreamHandler = async (event: DynamoDBStreamEvent) => {
await Promise.all(event.Records.map(async record => {
if (record.dynamodb?.NewImage?.vote?.S && record.dynamodb?.NewImage?.week?.S) {
await addVoteToResults(record.dynamodb.NewImage.vote.S, record.dynamodb.NewImage.week.S)
}
}))
}
where addVoteToResults is something like:
export const addVoteToResults = async (vote: string, week: string) => {
await dynamoDbClient.update({
TableName: 'table_name',
Key: { week: week },
UpdateExpression: 'add #vote :inc',
ExpressionAttributeNames: {
'#vote': vote
},
ExpressionAttributeValues: {
':inc': 1
}
}).promise();
}
Afterwards, when the voting is closed, you can retrieve the aggregated votes per week with a single get statement. This solution also helps spreading the write/read load rather than having a huge increase when executing your search function.
I have a very complex setup on my tables and achieving this via any of the find() methods is not an option for me, since I would need to fix relationships between my tables and I don't have the time right now, so I'm looking for a simple fix here.
All I want to achieve is run a query like this:
SELECT MAX( id ) as max FROM MyTable WHERE another_field_id = $another_field_id
Then, I need to assign that single id to a variable for later use.
The way I have it now it returns something like [{{max: 16}}], I'm aware I may be able to do some PHP on this result set to get the single value I need, but I was hoping there was already a way to do this on CakePHP.
Assuming you have a model for your table and your are using CakePHP 2.x, do:
$result = $this->MyTable->field('id', array('1=1'), 'id DESC');
This will return a single value.
see Model::field()
This example is directly from the CakePHP documentation. it seems you can use the find method of a model to get count
$total = $this->Article->find('count');
$pending = $this->Article->find('count', array(
'conditions' => array('Article.status' => 'pending')
));
$authors = $this->Article->User->find('count');
$publishedAuthors = $this->Article->find('count', array(
'fields' => 'DISTINCT Article.user_id',
'conditions' => array('Article.status !=' => 'pending')
));