MongoDB how to update array of objects with id and updated values - node.js

I have array of objects:
[
{_id: "5ca2141da0106d1320c0ae32", detail: 1},
{_id: "5ca2141da0106d1320c0ae33", detail: 3},
{_id: "5ca2141da0106d1320c0ae34", detail: 3}
]
How i can update document by id and its value from array?
I'm trying to do it this way:
arrayOfObjects.map(i => {
ids.push(i._id);
details.push(i.detail);
});
Model.updateMany(
{ _id: { $in: ids } },
{ $set: { detail: details} },
should i get an index of $in: ids for my set operator?

Here is your array.
var arrayofObjects = [
{_id: "5ca2141da0106d1320c0ae32", detail: 1},
{_id: "5ca2141da0106d1320c0ae33", detail: 3},
{_id: "5ca2141da0106d1320c0ae34", detail: 3}
]
arrayOfObjects.forEach(function(obj) {
Model.update({"_id": obj._id}, {"$set": {"detail": obj.detail}}, callback);
});
UpdateMany is db.Collection collection object. Refer link.

Prior to Mongo version 4.2 you have to execute each update separately as they have different conditions, this is best done using bulkWrite in order to save up on network overhead, like so:
const input = [
{_id: ObjectId("5ca2141da0106d1320c0ae32"), detail: 1},
{_id: ObjectId("5ca2141da0106d1320c0ae33"), detail: 3},
{_id: ObjectId("5ca2141da0106d1320c0ae34"), detail: 3}
];
const bulk = [];
input.forEach((datum) => {
bulk.push(
{
updateOne: {
"filter": {_id: datum._id},
"update": {$set: {detail: datum.detail}}
}
}
)
})
await db.collection.bulkWrite(bulk)
Version 4.2 introduced pipelined updates which allows us to use aggregation operators in the update body, now we can execute a single update and leverage this power, you can achieve the update in multiple ways here is one example:
const input = [
{_id: ObjectId("5ca2141da0106d1320c0ae32"), detail: 1},
{_id: ObjectId("5ca2141da0106d1320c0ae33"), detail: 3},
{_id: ObjectId("5ca2141da0106d1320c0ae34"), detail: 3}
];
db.collection.updateMany(
{ _id: {$in: input.map(v => v._id )}},
[
{
$set: {
detail: {
$getField: {
field: "detail",
input: {
$first: {
$filter: {
input: input,
cond: {
$eq: [
"$$this._id",
"$_id"
]
}
}
}
}
}
}
}
}
])
Mongo Playground

Related

mongoDB aggregation- unwind, sort and group

Considering I have a users collection contains those documents:
{
_id: 1,
hobbies: ['eat', 'read', 'swim']
},
{
_id: 2,
hobbies: ['eat', 'sleep', 'swim']
},
{
_id: 3,
hobbies: ['code', 'read', 'eat']
}
I want to do an aggregation on this collection so the result will be a distinct list of all those hobbies sorted in alphabetic order, for example:
{
result: [code, eat, read, sleep, swim]
}
I've tried this solution, but it didn't work for me:
{
$unwind: {path: "$hobbies"},
$group: {_id: null, result: {$addToSet: "$hobbies"}}
}
My problem is to sort the result field...
Your approach is very close already. Just remember $unwind and $group are separate pipeline stages. You need to wrap them with individual curly brackets. And for the sorting, you can do a $sortArray at the end of the pipeline.
db.collection.aggregate([
{
$unwind: {
path: "$hobbies"
}
},
{
$group: {
_id: null,
result: {
$addToSet: "$hobbies"
}
}
},
{
$set: {
result: {
$sortArray: {
input: "$result",
sortBy: 1
}
}
}
}
])
Mongo Playground

Pull element from an array of arrays MongoDb

I am struggling to find the format for a query to remove an element (with an _id) from an array of arrays in Mongo.
When looking at the docs I couldn't find anything that was similar to what I have
https://www.mongodb.com/docs/manual/reference/operator/update/pull/#up._S_pull
I know: the _id of the document in MySchema and the _id of the array element in innerArray.
I don't know the outerArray _id
Could someone help point out where I went wrong Thank you!
This is an example of the data (imagine the _id in ObjectId)
{
outerArray:[
{
_id: 1
innerArray: [{_id: 23, name: '123'}, {_id: 13, name: 'asdac'} ]
},
{
_id: 2,
innerArray: [{_id: 16,name:'asf' }, {_id: 18,name:'asf' } ]
},
{
_id: 3,
innerArray: [{_id: 136,name:'asf' }, {_id: 128,name:'asf' } ]
}
]
}
innerIds is an array of mongoose.Types.ObjectId
return MySchema.updateOne(
{
_id: documentId,
},
{ $pull: { outerArray: { innerArray: { _id: { $in: innerIds } } } } },
)
.session(session)
.exec()
db.collection.update({},
{
$pull: {
"outerArray.$[].innerArray": {//$[] does the trick
_id: {
$in: [
16
]
}
}
}
})
playground

MongoDB $sort on $concatArrays

I'm trying to concatenate two nested arrays (using $concatArrays) into one new field. I'd like to sort the output of the concatenation (Model.timeline) by a property that exists in both sets of objects. I can't seem to get it working with $unwind. Here's the query without any sorting:
Model.aggregate([
{
$match: {
'id': id
}
},
{
$project: {
id: 1,
name: 1,
flagged: 1,
updatedAt: 1,
lastEvent: {
$arrayElemAt: ['$events', -1]
},
lastimage: {
$arrayElemAt: ['$images', -1]
},
timeline: {
$concatArrays: [
{ $filter: {
input: '$events',
as: 'event',
cond: { $and: [
{ $gte: ['$$event.timestamp', startAt] },
{ $lte: ['$$event.timestamp', endAt] }
]}
}},
{ $filter: {
input: '$images',
as: 'image',
cond: { $and: [
{ $gte: ['$$image.timestamp', startAt] },
{ $lte: ['$$image.timestamp', endAt] }
]}
}}
]
}
}
}
]);
Am I missing something obvious?
You need three pipeline stages after your match and project. First $unwind, then $sort and then re $group. Use the $first operator to retain all the fields.
{
$undwind : "$timeline",
},
{
$sort : {"your.sortable.field" : 1}
},
{
$group : {
_id : "$_id",
name : {$first : 1},
flagged : {$first : 1},
updatedAt : {$first : 1},
lastEvent : {$first : 1},
lastimage : {$first : 1},
timeline : {$push : "$timeline"}
}
}
Please note that this will work even when you have more than one document after the match phase. So basically this will sort the elements of an array within each document.
Your $match and $project aggregation stages worked after I substituted id with _id, and filled in the values for id, startAt and endAt like so:
db.items.aggregate([
{
$match: {
'_id': '58'
}
},
{
$project: {
'_id': 1,
name: 1,
flagged: 1,
updatedAt: 1,
lastEvent: {
$arrayElemAt: ['$events', -1]
},
lastimage: {
$arrayElemAt: ['$images', -1]
},
timeline: {
$concatArrays: [
{ $filter: {
input: '$events',
as: 'event',
cond: { $and: [
{ $gte: ['$$event.timestamp', ISODate("2016-01-19T20:15:31Z")] },
{ $lte: ['$$event.timestamp', ISODate("2016-12-01T20:15:31Z")] }
]}
}},
{ $filter: {
input: '$images',
as: 'image',
cond: { $and: [
{ $gte: ['$$image.timestamp', ISODate("2016-01-19T20:15:31Z")] },
{ $lte: ['$$image.timestamp', ISODate("2016-12-01T20:15:31Z")] }
]}
}}
]
}
}
}
]);

NodeJS MongoDB Pass parameter in the aggregation pipeline

How to pass an parameter to the aggregation?
I'm getting the params and trying to pass it using $match operator but query returns empty array:
app.get('/api/:name', function(req, res){
var name = req.params.name;
console.log(name);
db.collection('coll').aggregate([{$match: {name: '$name'}}, {$unwind: { path: "$dates", includeArrayIndex: "idx" } }, { $project: { _id: 0, dates: 1, numbers: { $arrayElemAt: ["$numbers", "$idx"] }, goals: { $arrayElemAt: ["$goals", "$idx"] }, durations: { $arrayElemAt: ["$durations", "$idx"]}}}]).toArray(function(err, docs) {
if (err) {
assert.equal(null);
}
else {
console.log(docs);
res.json(docs);
}
});
})
Should I care about order of the operators in pipeline?
Try the following code:-
app.get('/api/:name', function(req, res){
var name = req.params.name;
var query = [{$match: {'name': name}}, {$unwind: { path: "$dates", includeArrayIndex: "idx" } }, { $project: { _id: 0, dates: 1, numbers: { $arrayElemAt: ["$numbers", "$idx"] }, goals: { $arrayElemAt: ["$goals", "$idx"] }, durations: { $arrayElemAt: ["$durations", "$idx"]}}}];
db.collection('coll').aggregate(query).toArray(function(err, docs) {
if (err) {
assert.equal(null);
}
else {
console.log(docs);
res.json(docs);
}
});
})
It seems you never use variable called name.
Try this, change {$match: {name: '$name'} to {$match: {name: name}.
try,
{$match: {'name': req.params.name}}
this works for me
Mongoose does not cast pipeline stages. The below will not work unless _id is a string in the database
new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]);
// Do this instead to cast to an ObjectId
new Aggregate([{ $match: { _id:
mongoose.Types.ObjectId('00000000000000000000000a') } }]);
API URL: https://mongoosejs.com/docs/api.html#aggregate_Aggregate

How to use more then $group in mongoose

[
{
"_id":"56569bff5fa4f203c503c792",
"Status":{
"StatusID":2,
"StatusObjID":"56559aad5fa4f21ca8492277",
"StatusValue":"Closed"
},
"OwnerPractice":{
"PracticeObjID":"56559aad5fa4f21ca8492291",
"PracticeValue":"CCC",
"PracticeID":3
},
"Name":"AA"
},
{
"_id":"56569bff5fa4f203c503c792",
"Status":{
"StatusID":2,
"StatusObjID":"56559aad5fa4f21ca8492277",
"StatusValue":"Open"
},
"OwnerPractice":{
"PracticeObjID":"56559aad5fa4f21ca8492292",
"PracticeValue":"K12",
"PracticeID":2
},
"Name":"BB"
}
]
In above json response,
How to group by PracticeValue,StatusValue into single function,
the below code to be used to group only StatusValue,please help how to group Practice value with the same function,
Opp.aggregate([
{$group: {
_id: '$Status.StatusValue',
count: {$sum: 1}
}}
], function (err, result) {
res.send(result);
});
and my response is,
[
{
"_id":"Deleted",
"count":0
},
{
"_id":"Open",
"count":1
},
{
"_id":"Closed",
"count":1
}
]
please help me, how to use more then $group function..
You can group by multiple fields like this:
var resultAggr = {Status: [], Practice: []};
Opp.aggregate(
[
{$group: { _id: '$Status.StatusValue', count: {$sum: 1} }}
], function (err, statusResult) {
resultAggr.Status = statusResult;
Opp.aggregate(
[
{$group: { _id: '$OwnerPractice.PracticeValue', count: {$sum: 1} }}
], function (err, practiceResult) {
resultAggr.Practice = practiceResult;
res.send([resultAggr])
});
});

Resources