$regex within $project mongodb aggregation - mongodb

I have a users collection which has a location field with type String. I want to pass a score field along the document which shows whether the document's location is similar to the text "Austin". For instance the recorded location is Austin, Texas, I want it to be matched with Austin. I thought it would be possible to use $regex for this.
I wrote this aggregation:
$project: {
score: {
$cond:
if: {
$regex: {'$location': /Austin/}
},
then: 1,
else: 0
}
},
location: 1,
firstName: 1,
lastName: 1
}
But what I get is :
{
"name": "MongoError",
"errmsg": "exception: invalid operator '$regex'",
"code": 15999,
"ok": 0
}

I know this is an old question but since version 4.2 you can use $regexMatch.
You can use this stage:
{
$project: {
score: {
$cond: {
if: {
$regexMatch: {
input: "$location",
regex: "Austin"
}
},
then: 1,
else: 0
}
},
location: 1,
firstName: 1,
lastName: 1
}
}
Example here

Related

MongoDB - arrayFilters and updateMany from same document

I have the following sample of data:
{
_id: 1,
seniorityDate: '2001-01-01T00:00:00Z',
assigned: [
{
groupId: 11,
system: 'Dep',
effectiveDate: null
},
{
groupId: 12,
system: 'Team',
effectiveDate: null
},
...
]
}
and I would like to update the object effectiveDate based on seniorityDate in the array of assigned where system:'Team' only:
db.collection.updateMany({},
[{
$set: {
'assigned.$[elem].effectiveDate': '$seniorityDate'
}
}], {
arrayFilters: [{
"elem.system": "Team"
}]
})
but I got the following error:
arrayFilters may not be specified for pipeline-syle updates
The expected result will be:
{
_id: 1,
seniorityDate: '2001-01-01T00:00:00Z',
assigned: [
{
groupId: 11,
system: 'Dep',
effectiveDate: null
},
{
groupId: 12,
system: 'Team',
effectiveDate: '2001-01-01T00:00:00Z'
},
...
]
}
How can I achieve it?
You can't use the arrayFilters with the aggregation pipeline at the same time. While you are updating the value from another field, hence you can only achieve with aggregation pipeline.
$set - Set assigned field.
1.1. $map - Iterate element in assigned array and return new array.
1.1.1. $mergeObjects - Merge current iterated document with the document from 1.1.1.1.
1.1.1.1. Document with effectiveDate field. With the $cond operator, if matches the condition, use the seniorityDate value, else remain the existing value.
db.collection.updateMany({},
[
{
$set: {
"assigned": {
$map: {
input: "$assigned",
in: {
$mergeObjects: [
"$$this",
{
effectiveDate: {
$cond: {
if: {
$eq: [
"$$this.system",
"Team"
]
},
then: "$seniorityDate",
else: "$$this.effectiveDate"
}
}
}
]
}
}
}
}
}
])
Thanks to #rickhg12hs' suggestion, always limit the document for better performance, as you know which document/field should be updated by condition.
Hence your update query with query condition will be as below:
db.collection.updateMany({
"assigned.system": "Team"
},
[
...
])
Demo # Mongo Playground

Add number field in $project mongodb

I have an issue that need to insert index number when get data. First i have this data for example:
[
{
_id : 616efd7e56c9530018e318ac
student : {
name: "Alpha"
email: null
nisn: "0408210001"
gender : "female"
}
},
{
_id : 616efd7e56c9530018e318af
student : {
name: "Beta"
email: null
nisn: "0408210001"
gender : "male"
}
}
]
and then i need the output like this one:
[
{
no:1,
id:616efd7e56c9530018e318ac,
name: "Alpha",
nisn: "0408210001"
},
{
no:2,
id:616efd7e56c9530018e318ac,
name: "Beta",
nisn: "0408210002"
}
]
i have tried this code but almost get what i expected.
{
'$project': {
'_id': 0,
'id': '$_id',
'name': '$student.name',
'nisn': '$student.nisn'
}
}
but still confuse how to add the number of index. Is it available to do it in $project or i have to do it other way? Thank you for the effort to answer.
You can use $unwind which can return an index, like this:
db.collection.aggregate([
{
$group: {
_id: 0,
data: {
$push: {
_id: "$_id",
student: "$student"
}
}
}
},
{
$unwind: {path: "$data", includeArrayIndex: "no"}
},
{
"$project": {
"_id": 0,
"id": "$data._id",
"name": "$data.student.name",
"nisn": "$data.student.nisn",
"no": {"$add": ["$no", 1] }
}
}
])
You can see it works here .
I strongly suggest to use a $match step before these steps, otherwise you will group your entire collection into one document.
You need to run a pipeline with a $setWindowFields stage that allows you to add a new field which returns the position of a document (known as the document number) within a partition. The position number creation is made possible by the $documentNumber operator only available in the $setWindowFields stage.
The partition could be an extra field (which is constant) that can act as the window partition.
The final stage in the pipeline is the $replaceWith step which will promote the student embedded document to the top-level as well as replacing all input documents with the specified document.
Running the following aggregation will yield the desired results:
db.collection.aggregate([
{ $addFields: { _partition: 'students' }},
{ $setWindowFields: {
partitionBy: '$_partition',
sortBy: { _id: -1 },
output: { no: { $documentNumber: {} } }
} },
{ $replaceWith: {
$mergeObjects: [
{ id: '$_id', no: '$no' },
'$student'
]
} }
])

Mongo aggregation: add field derived from another field

I have documents of the following form present in a collection:
{
name: "Michael",
likes: [{ name: "reading", amount: 80}, {name: "eating", amount: 70}]
}
I want to add a new field to all the documents which will be 0/1 depending on whether the person likes reading. I have to check for the presence of likes.name = "reading".
I tried adding the following stage in my aggregation pipeline to achieve this:
$addFields: {
likes_reading: {
$cond: {
if: {
$eq: ["$likes.name", "reading"]
},
then: 1,
else: 0
}
}
}
However, $eq doesn't seem to be checking if "any" element of likes array has the required name, which is the kind of behaviour I've seen in many other places when we compare values with arrays. As a result all the likes_reading are set to 0. I need help setting this new field properly.
Since likes is an array you have to use $in operator so try this:
{
$addFields: {
likes_reading: {
$cond: {
if: { $in: ["reading", "$likes.name"] },
then: 1,
else: 0
}
}
}
}
Or
{
$addFields: {
likes_reading: {
$cond: [{ $in: ["reading", "$likes.name"] }, 1, 0]
}
}
}

MongoDB: Find and then modify the resulting object

is it possible in MongoDB to find some objects that match a query and then to modify the result without modifying the persistent data?
For example, let
students = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 22 },
{ name: "Carol", age: 19 },
{ name: "Dave", age: 18}
]
Now, I want to query all students that are younger than 20 and in the search result, I just want to replace "age: X" with "under20: 1" resulting in the following:
result = [
{ name: "Carol", under20: 1 },
{ name: "Dave", under20: 1}
]
without changing anything in the database.
Sure, it is possible to get the result and then call a forEach on it, but that sounds so inefficient because I have to rerun every object again, so I'm searching for an alternative. Or is there no one?
A possible solution would be to use an aggregation pipline with a $match followed by a $project:
db.students.aggregate(
[
{
$match: { age: { $lt: 20 } }
},
{
$project:
{
_id: false,
name: true,
under20: { $literal: 1 }
}
}
]);
The $literal: 1 is required as just using under20: 1 is the same as under20: true, requesting that field under20 be included in the result: which would fail as under20 does not exist in the document produced by the match.
Or to return all documents in students and conditionally generate the value for under20 a possible solution would be to use $cond:
db.students.aggregate(
[
{
$project:
{
_id: false,
name: true,
under20:
{
$cond: { if: { $lt: [ "$age", 20 ] }, then: 1, else: 0 }
}
}
}
]);

MongoDB: match non-empty doc in array

I have a collection structured thusly:
{
_id: 1,
score: [
{
foo: 'a',
bar: 0,
user: {user1: 0, user2: 7}
}
]
}
I need to find all documents that have at least one 'score' (element in score array) that has a certain value of 'bar' and a non-empty 'user' sub-document.
This is what I came up with (and it seemed like it should work):
db.col.find({score: {"$elemMatch": {bar:0, user: {"$not":{}} }}})
But, I get this error:
error: { "$err" : "$not cannot be empty", "code" : 13030 }
Any other way to do this?
Figured it out: { 'score.user': { "$gt": {} } } will match non-empty docs.
I'm not sure I quite understand your schema, but perhaps the most straight forward way would be to not have an "empty" value for score.user ?
Instead purposely not have that field in your document if it has no content?
Then your query could be something like ...
> db.test.find({ "score" : { "$elemMatch" : { bar : 0, "user" : {"$exists": true }}}})
i.e. looking for a value in score.bar that you want (0 in this case) checking for the mear existence ($exists, see docs) of score.user (and if it has a value, then it'll exist?)
editied: oops I missed the $elemMatch you had ...
You probably want to add an auxiliary array that keeps track of the users in the user document:
{
_id: 1,
score: [
{
foo: 'a',
bar: 0,
users: ["user1", "user2"],
user: {user1: 0, user2: 7}
}
]
}
Then you can add new users atomically:
> db.test.update({_id: 1, score: { $elemMatch: {bar: 0}}},
... {$set: {'score.$.user.user3': 10}, $addToSet: {'score.$.users': "user3"}})
Remove users:
> db.test.update({_id: 1, score: { $elemMatch: {bar: 0}}},
... {$unset: {'score.$.user.user3': 1}, $pop: {'score.$.users': "user3"}})
Query scores:
> db.test.find({_id: 1, score: {$elemMatch: {bar: 0, users: {$not: {$size: 0}}}}})
If you know you'll only be adding non-existent users and removing existent users from the user document, you can simplify users to a counter instead of an array, but the above is more resilient.
Look at the $size operator for checking array sizes.
$group: {
_id: '$_id',
tasks: {
$addToSet: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.id', ''],
},
'',
],
},
then: '$$REMOVE',
else: {
id: '$tasks.id',
description: '$tasks.description',
assignee: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.assignee._id', ''],
},
'',
],
},
then: undefined,
else: {
id: '$tasks.assignee._id',
name: '$tasks.assignee.name',
},
},
},
},
},
},
},