Mongodb $match using field added with $addFields - mongodb

How can I use a field that I just added in the $addFields stage to the following $match stage?
This will return no result:
db.getCollection('myCollection').aggregate([
{$addFields: { "test": ISODate("2018-02-15T03:22:21.000Z")}},
{$match: { $or: [{"timestamp":"$test"}]}}
])
This one will return expected result:
db.getCollection('myCollection').aggregate([
{$addFields: { "test": ISODate("2018-02-15T03:22:21.000Z")}},
{$match: { $or: [{"timestamp":ISODate("2018-02-15T03:22:21.000Z")}]}}
])
How comes that the $test is not resolved in the $match stage?
EDIT
I finally post myself a solution for mongdb 2.4 thanks to this answer. Solutions are similar but the problem are not expressed the same way

I think Veeram answer is valid for mongodb version 3.6 but I'm running with version 3.4
I finally found this way to be able to use a field added in a $addFields stage:
db.getCollection('myCollection').aggregate([
{$addFields: { "myDate": ISODate("2018-02-15T03:22:21.000Z")}},
{$project:{test: {$cond:[{$eq:["$timestamp", "$myDate"]},1,0]}}},
{$match: {"test": {"$eq": 1}}}
])
or with the $redact stage:
db.getCollection('myCollection').aggregate([
{$addFields: { "test": ISODate("2018-02-15T03:22:21.000Z")}},
{$redact: {$cond: [{ $eq: [ "$timestamp", "$test" ] }, "$$KEEP", "$$PRUNE"]}}
])
I do not know why it is working with (addFields + project + match) and with (addFields + redact) but not with (addFields + match) but I will go with this solution as migration is not yet foreseen

Related

How to iterate through a set to get field value in MongoDB

Can somebody tell me please if is possible to iterate through a set to create a field value for key in mongodb result. If I have $facet state in pipeline like:
'missing': [{'$group': {'_id': '$foo', 'woo': {'$addToSet': '$wwo'}}},
{'$project': {'missing_woo': {'$setDifference': [woo_set, '$woo']}}
I would like to get result where code value will be the key like
{'missing_woo': 'missing_woo1'}, {'missing_woo': 'missing_woo2'},... {'missing_woo': 'missing_wooN'}
so that I can iterate through the set generated at $project and to create field values
You can simply use $unwind:
db.collection.aggregate([
{
$facet: {
missing: [
{$group: {_id: "$foo", woo: {$addToSet: "$wwo"}}},
{$project: {_id: 0, missing_woo:
{$setDifference: [
[
"woo1",
"woo2",
"wooN",
"missing_woo1",
"missing_woo2",
"missing_wooN"
],
"$woo"
]
}
}
},
{$unwind: "$missing_woo"}
]
}
}
])
See how it works on the playground example

How to use calcuted date in a mongodb filter?

I want to filter for documents that are within the last minute. But the following query, with a calculated $gte time, does not work.
db.mycollection.aggregate([
{$match: {
"properties.time": {$gte: {$subtract: [ISODate(), 60000]}}
}}
])
It doesn't return anything, but not because there are no dates within the last minute. As far I can tell from projecting the calculated date in queries that do work, the calculated date is correct. Is there some reason that you cannot use a calculated value in this way?
The following query, with a hard-coded time, does work:
db.mycollection.aggregate([
{$match: {
"properties.time": {$gte: ISODate("2018-12-26T12:00:00Z")}
}}
])
I also considered calculating the age in seconds and then filtering based on that. The following also does work.
db.mycollection.aggregate([
{$project: {
"properties.time": "$properties.time",
"age": {$subtract: [ISODate(), "$properties.time"]}
}},
{$match: {
"age": {$lte: 60000}
}}
])
But that's not a good solution because
really, I want to include the filter in a $geoNear query, and since $geoNear has to be the first step in the pipeline, I can't project the age and then use it in the $geoNear
I want to use the index on "properties.time"
We're using eve, so another workaround would be to convert the requested max_age parameter to a start_time in a hook. But it really seems like that first approach should work.
You are using an aggregation expression inside $match so you have to use $expr operator, try:
db.mycollection.aggregate([
{
$match: {
$expr: {
$gte: [ "$properties.time", { $subtract: [ISODate(), 60000] } ]
}
}
}
])
You can use $redact as a fallback for MongoDB lower than 3.6, try:
db.mycollection.aggregate([
{
$redact: {
$cond: {
if: { $gte: [ "$properties.time", { $subtract: [ISODate(), 60000] } ] },
then: "$$KEEP",
else: "$$PRUNE"
}
}
}
])

In mongodb aggregation where to apply sort before lookup or after lookup?

I am writing an aggregation query where i want to perform a join in MongoDB between two collections and for that i am using $lookup, now my question is does $lookup change order of results by sort or not ?? because if it does that then i need to put my sort after $lookup and if not then i can use it before $lookup ??
My code is given below
brandmodel.aggregate(
{$project: { '_id':0, 'brand_id': 1, 'brand_name':1, 'brand_icon':1, 'banner_image': 1, 'weight': 1} },
{$lookup: {from: "student_coupons",localField: "brand_id",foreignField: "brand_id",as: "coupons"}},
{$unwind : "$coupons"},
{$sort: {weight: -1, "coupons.time_posted": -1}}, // SHOULD I WRITE THIS BEFORE LOOKUP OR AFTER LOOKUP
In MongoDB 3.6, the $lookup has a more expressive way where you can access the fields of the source document and do further pipeline operations within the $lookup stage. See documentation
As an example,
db.movies.aggregate([
{ $match : { _id : ObjectId("573a1390f29313caabcd414c")} },
{ $lookup : {from: "comments",
let: {'id' : '$_id' },
pipeline: [
{ $match : { '$expr': { '$eq': [ '$movie_id', '$$id' ] } }},
{ $sort: {'date': -1} }
],
as: "comments"
}
}
])
You have to declare any fields you want from the source collection in the let , do the matching as required (This is optional). Then you can use the pipeline stages that you need to apply in the collection being looked up.

Mongodb 3.2 and 3.0 $unwind aggregation

I have created a query and check it in robomongo and it's working fine for me in mongodb 3.2
db.post.aggregate([
{$unwind: {path: "$page_groups", preserveNullAndEmptyArrays: true}},
{$group: {_id: "$page_groups",
page_names: {$addToSet: "$page_name"}}},
])
But unfortunantly I need to get same data in mongodb 3.0
Can anyone tell me how to get data with empty array in mongo 3.0 and get results by array key?
Without $unwind I get objects where pages have two or more groups and I don't need it.
Thank you for answere, I wanted to use $project at first, but I think I have found easier way using $match and array $size to ignore results where array gets more than one element:
db.post_summary.aggregate([
{$match: {$or:
[{page_groups: {$size: 1}}, {page_groups: {$size: 0}}]}},
{$group: {
_id: "$page_groups",
page_names: { "$addToSet": "$page_name" }
}},
])
In my case "page_groups" have this structure:
page_groups:[
0 =>[_id, group_name]
1 =>[_id, group_name]
]
To mimick the preserveNullAndEmptyArrays $unwind option in 3.2 for 3.0 aggregation pipeline operations, generate an initial $project pipeline stage that creates the array field if it's null or empty (using the $ifNull operator):
var pipeline = [
{
"$project": {
"pg": {
"$ifNull": [
"$page_groups",
["Unspecified"]
]
},
"page_name": 1
}
},
{ "$unwind": "$page_groups" },
{
"$group": {
"_id": "$page_groups",
"page_names": { "$addToSet": "$page_name" }
}
}
];
db.collection.aggregate(pipeline);

How to aggregate queries in mongodb

I have a document collection that look like the following:
{
name : "tester"
, activity: [
{
gear: "glasses"
where: "outside"
}
, {
gear: "hat"
, where: "inside"
}
, {
gear: "glasses"
, where: "car"
}
]
}
How do I query the collection to return only documents with multiple activities that contain the value of "gear":"glasses"?
Thanks!
I think it's possible to do without aggregation framework, if you need full document filtered by your condition:
db.collection.find({
"activity": {$elemMatch: {gear:"glasses"}},
"activity.1" : {$exists: 1}
})
This is going to be ugly with aggregation framework, but it can be done:
db.collection.aggregate(
{$match: {"activity.gear": "glasses"}},
{$unwind: "$activity"},
{$group: {
_id: {_id: "$_id", name: "$name"},
_count: {$sum: {$cond: [{$eq: ["glasses", "$activity.gear"]}, 1, 0]}}
}},
{$match: {_count: {$gt: 1}}}
)
When analyzing the above query, I would recommend walking through step. Start with just the "$match", the the "$match" and "$unwind". And so one. You will see how each step works.
The response is not the full document. If you are looking for the full document, include a $project step that passes through a dummy activity, and reconstruct the full document on the output.
You can also try this:
db.collection.find( { activity: { $elemMatch: { gear: "glasses" } } )