MongoDB Add Field in nested array by other field - arrays

Hello i have simple collection:
{
_id: 1,
books: [
{ bookId: 55, c: 5},
{ bookId: 66, c: 6},
{ bookId: 77, c: 7},
]
}
How i can add new field by calulate other field?
here i add field “Z” to current found object in nested array by it calculate field “C”
updateOne(
{
_id : 1,
'books.bookId' : 66
} ,
{
[
{ $addFields: { "books.$.z" : { "$sum" : ["$books.$.c", 1] } } }
]
}
Expected result:
{
_id: 1,
books: [
{ bookId: 55, c: 5},
{ bookId: 66, c: 6, z:7},
{ bookId: 77, c: 7},
]
}
I think there is a short entry (possibly using new $getField ?!), I think mango is still able to combine ‘position operator $’ + (‘varible operator reference by prefix $’ or ‘combine with $getField’) how i try in my sample

Use the aggregation pipeline in the update method to leverage the operators $map, $mergeObjects and $cond to achieve the desired results:
.updateOne(
// The query will find docs where *at least* one bookId in the
// books array equals 66 -- but remember it does not pinpoint
// the location in the array! We will tackle that in the update
// pipeline to follow...
{ _id: 1, 'books.bookId': 66 },
[
{ $set: {
books: {
$map: {
input: '$books',
in: {
$mergeObjects: [
'$$this',
{
$cond: [
{ $eq: ['$$this.bookId', 66] }, // IF books.bookId == 66
{ z: { $sum: ['$$this.c', 1] } }, // THEN merge a new field 'z' with calced value
null // ELSE merge null (a noop)
]
}
]
}
}
}
} }
]
)

Related

MongoDB. Remove the oldest element from array inside document

I have this document:
[
{
"username_id": "user01",
"passwordList": [
{
"tstamp": 101,
"tempInd": 0,
"pass": "aaaa"
},
{
"tstamp": 102,
"tempInd": 0,
"pass": "bbbbb"
},
{
"tstamp": 103,
"tempInd": 0,
"pass": "ccccc"
},
{
"tstamp": 100,
"tempInd": 1,
"pass": "99999"
}
]
}
]
What I want is to remove from the passwordList the element which has the lowest tstamp with tempInd equal to 0. This is my expected output:
[
{
"username_id": "user01",
"passwordList": [
{
"tstamp": 102,
"tempInd": 0,
"pass": "bbbbb"
},
{
"tstamp": 103,
"tempInd": 0,
"pass": "ccccc"
},
{
"tstamp": 100,
"tempInd": 1,
"pass": "99999"
}
]
}
]
This is my attempt:
db.collection.update([
{"username_id": "user01" } ,
{"$pull":{"passwordList": { "$elemMatch": { "tempInd": 0 , "tstamp": {"$min": "$passwordList.tstamp"} } } } }
])
Any suggestion?
Thanks!
You can do it like this:
db.collection.update(
{ "username_id": "user01" } ,
[
{
$set: {
passwordList: {
$filter: {
input: '$passwordList',
as: 'filter1Password',
cond: {
$ne: [
'$$filter1Password',
{
$first: {
$sortArray: {
input: {
$filter: {
input: '$passwordList',
as: 'filter2Password',
cond: {
$eq: ['$$filter2Password.tempInd', 0]
}
}
},
sortBy: {
tstamp: 1
}
}
}
}
]
}
}
}
}
}
]
)
Working from the inside out:
The innermost $filter operator discards all array elements whose tempInd is not 0.
The $sortArray operator sorts the result of step 1 by tstamp, ascending. (note that $sortArray is only available in Mongo 5.2 and newer)
The $first operator returns the first element of the array returned by step 2 (this would be the element with the lowest tstamp whose tempInd is 0)
The $filter operator returns all elements of the passwordList array that are NOT equal to the result of step 3. (note that if the array has multiple elements that all match the result of step 3, all of them will be removed)
The $set operator sets passwordList to be the result of step 4.

How to use $getfield to get a field from ROOT Document with condition in Aggregation Mongodb

I'm starting to learn Aggregate in MongoDB. I have a simple Doc as below, which has 2 fields, name and examScores, examScores is an array contains multiplier documents:
{ _id: ObjectId("633199db009be219a43ae426"),
name: 'Max',
examScores:
[ { difficulty: 4, score: 57.9 },
{ difficulty: 6, score: 62.1 },
{ difficulty: 3, score: 88.5 } ] }
{ _id: ObjectId("633199db009be219a43ae427"),
name: 'Manu',
examScores:
[ { difficulty: 7, score: 52.1 },
{ difficulty: 2, score: 74.3 },
{ difficulty: 5, score: 53.1 } ] }
Now I query the maximum score of each person using $unwind and $group/$max as below:
db.test.aggregate([
{$unwind: "$examScores"},
{$group: {_id: {name: "$name"}, maxScore: {$max: "$examScores.score"}}}
])
{ _id: { name: 'Max' }, maxScore: 88.5 }
{ _id: { name: 'Manu' }, maxScore: 74.3 }
But I want the result also contains the examScores.difficulty field corresponding to name and examScores.score, like below:
{ _id: { name: 'Max' }, difficulty: 3, maxScore: 88.5 }
{ _id: { name: 'Manu' }, difficulty: 2, maxScore: 74.3 }
I know that I can use $sort + $group and $first to achieve this goal. But I want to use $getField or any other methods to get data from ROOT Doc.
My idea is use $project and $getField to get the difficulty field from ROOT doc (or $unwind version of ROOT doc) with the condition like ROOT.name = Aggregate.name and Root.examScores.score = Aggregate.maxScore.
It will look something like this:
{$project:
{name: 1,
maxScore: 1,
difficulty:
{$getField: {
field: "$examScores.difficulty"
input: "$$ROOT.$unwind() with condition/filter"}
}
}
}
I wonder if this is possible in MongoDB?
Solution 1
$unwind
$group - Group by name. You need $push to add the $$ROOT document into data array.
$project - Set the difficulty field by getting the value of examScores.difficulty from the first item of the filtered data array by matching the examScores.score with maxScore.
db.collection.aggregate([
{
$unwind: "$examScores"
},
{
$group: {
_id: {
name: "$name"
},
maxScore: {
$max: "$examScores.score"
},
data: {
$push: "$$ROOT"
}
}
},
{
$project: {
_id: 0,
name: "$_id.name",
maxScore: 1,
difficulty: {
$getField: {
field: "difficulty",
input: {
$getField: {
field: "examScores",
input: {
$first: {
$filter: {
input: "$data",
cond: {
$eq: [
"$$this.examScores.score",
"$maxScore"
]
}
}
}
}
}
}
}
}
}
}
])
Demo Solution 1 # Mongo Playground
Solution 2: $rank
$unwind
$rank - Ranking by partition name and sort examScores.score descending.
$match - Filter the document with { rank: 1 }.
$unset - Remove rank field.
db.collection.aggregate([
{
$unwind: "$examScores"
},
{
$setWindowFields: {
partitionBy: "$name",
sortBy: {
"examScores.score": -1
},
output: {
rank: {
$rank: {}
}
}
}
},
{
$match: {
rank: 1
}
},
{
$unset: "rank"
}
])
Demo Solution 2 # Mongo Playground
Opinion: I would say this approach:
$sort by examScores.score descending
$group by name, take the first document
would be much easier.
There's no need to $unwind and then rebuild the documents again via $group to achieve your desired results. I'd recommend avoiding that altogether.
Instead, consider processing the arrays inline using array expression operators. Depending on the version and exact results you are looking for, here are two starting points that may be worth considering. In particular the $maxN operator and the $sortArray operator may be of interest for this particular question.
You can get a sense for what these two operators do by running an $addFields aggregation to see their output, playground here.
With those as a starting point, it's really up to you to make the pipeline output the desired result. Here is one such example that matches the output you described in the question pretty well (playground):
db.collection.aggregate([
{
"$addFields": {
"relevantEntry": {
$first: {
$sortArray: {
input: "$examScores",
sortBy: {
"score": -1
}
}
}
}
},
},
{
"$project": {
_id: 0,
name: 1,
difficulty: "$relevantEntry.difficulty",
maxScore: "$relevantEntry.score"
}
}
])
Which yields:
[
{
"difficulty": 3,
"maxScore": 88.5,
"name": "Max"
},
{
"difficulty": 2,
"maxScore": 74.3,
"name": "Manu"
}
]
Also worth noting that this particular approach doesn't do anything special if there are duplicates. You could look into using $filter if something more was needed in that regard.

How to perform update query on deeply nested JSON in mongodb?

I have a nested json data structure in mongodb which looks like:
{
'tid': 1,
'matches': [{
'dord': 1,
'matches': [{
'tord': 1,
'score': 11
},
{
'tord': 2,
'score': 12
}
]
},
{
'dord': 2,
'matches': [{
'tord': 1,
'score': 21
},
{
'tord': 2,
'score': 22
}
]
}]
}
I want to update the row with "dord": 1 and "tord": 1 and change value of score from 11 to 100. How do I do this?
What I already tried:
db.collection.update({'tid': 1}, {'matches': {$elemMatch: {'dord': 1}}}, {'matches': { $elemMatch: {'tord': 1}}}, {'score': 100})
Demo - https://mongoplayground.net/p/Mi2HnhzkPpE
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/
The filtered positional operator $[] identifies the array elements that match the arrayFilters conditions for an update operation
db.collection.update({ "tid": 1 },
{ $set: { "matches.$[m].matches.$[t].score": 100 } },
{
arrayFilters: [
{ "m.dord": 1 }, // to match where dord = 1
{ "t.tord": 1, "t.score": 11 } // and where tord = 1 and score = 11
]
})

Mongodb update nested array by key-value

How can I update nested array by list key value?
{
"_id": "mainId",
"events": [{
"id": 1,
"profile": 10,
} {
"id": 2,
"profile": 10,
} {
"id": 3,
"profile": 20,
}
]
}
and I have a list to update:
var list = {id: 2, newval: 222}, {id: 3, newval: 333}
How can I do an update in one query? Or in MongoDB, it will be like a loop?
for({id, val} in list){
update({_id: "mainId", events.$.id: id}, {setField: {events.$.profile: val}})
}
If you have a copy of the events array, you could make the necessary updates in your code and then send the updated array to MongoDB in a single query. Something like this
db.Test.updateOne({_id: "mainId"}, {$set: { "events": [{id: 1, profile: 222}, {id: 2, profile: 10}, {id: 3, profile: 333}] } } )
If you don't have a copy of the events array, you could do a bulk operation. Something like
db.Test.bulkWrite(
[
{ updateOne : {
"filter": {_id: "mainId", "events.id": 1},
"update": { $set: { "events.$.profile": 222 } }
}
},
{ updateOne : {
"filter": {_id: "mainId", "events.id": 3},
"update": { $set: { "events.$.profile": 333 }}
}
}
]
)
For more on bulkWrite, see the MongoDB docs: https://docs.mongodb.com/manual/core/bulk-write-operations/#bulkwrite-methods

Move an element from one array to another within same document MongoDB

I have data that looks like this:
{
"_id": ObjectId("4d525ab2924f0000000022ad"),
"array": [
{ id: 1, other: 23 },
{ id: 2, other: 21 },
{ id: 0, other: 235 },
{ id: 3, other: 765 }
],
"zeroes": []
}
I'm would like to to $pull an element from one array and $push it to a second array within the same document to result in something that looks like this:
{
"_id": ObjectId("id"),
"array": [
{ id: 1, other: 23 },
{ id: 2, other: 21 },
{ id: 3, other: 765 }
],
"zeroes": [
{ id: 0, other: 235 }
]
}
I realize that I can do this by doing a find and then an update, i.e.
db.foo.findOne({"_id": param._id})
.then((doc)=>{
db.foo.update(
{
"_id": param._id
},
{
"$pull": {"array": {id: 0}},
"$push": {"zeroes": {doc.array[2]} }
}
)
})
I was wondering if there's an atomic function that I can do this with.
Something like,
db.foo.update({"_id": param._id}, {"$move": [{"array": {id: 0}}, {"zeroes": 1}]}
Found this post that generously provided the data I used, but the question remains unsolved after 4 years. Has a solution to this been crafted in the past 4 years?
Move elements from $pull to another array
There is no $move in MongoDB. That being said, the easiest solution is a 2 phase approach:
Query the document
Craft the update with a $pull and $push/$addToSet
The important part here, to make sure everything is idempotent, is to include the original array document in the query for the update.
Given a document of the following form:
{
_id: "foo",
arrayField: [
{
a: 1,
b: 1
},
{
a: 2,
b: 1
}
]
}
Lets say you want to move { a: 1, b: 1 } to a different field, maybe called someOtherArrayField, you would want to do something like.
var doc = db.col.findOne({_id: "foo"});
var arrayDocToMove = doc.arrayField[0];
db.col.update({_id: "foo", arrayField: { $elemMatch: arrayDocToMove} }, { $pull: { arrayField: arrayDocToMove }, $addToSet: { someOtherArrayField: arrayDocToMove } })
The reason we use the $elemMatch is to be sure the field we are about to remove from the array hasn't changed since we first queried the document. When coupled with a $pull it also isn't strictly necessary, but I am typically overly cautious in these situations. If there is no parallelism in your application, and you only have one application instance, it isn't strictly necessary.
Now when we check the resulting document, we get:
db.col.findOne()
{
"_id" : "foo",
"arrayField" : [
{
"a" : 2,
"b" : 1
}
],
"someOtherArrayField" : [
{
"a" : 1,
"b" : 1
}
]
}

Resources