Update nested array element in mongodb - mongodb

In my case I have mongodb document with nested arrays and I need to update attributes array elements. My mongodb document as follows.
{
"_id" : ObjectId("5dca9c5ece5b91119746eece"),
"updatedAt" : ISODate("2019-11-20T11:48:55.339Z"),
"attributeSet" : [
{
"attributeSetName" : "test-0",
"type" : "Product",
"id" : "1574158032603",
"updatedAt" : ISODate("2019-11-19T10:10:53.783Z"),
"createdAt" : ISODate("2019-11-19T10:07:20.084Z"),
"attributes" : [
{
"attributeName" : "test-attribute",
"defaultValue" : 123,
"isRequired" : false,
"id" : "1574221129398"
},
{
"attributeName" : "test-attribute-02",
"defaultValue" : 456,
"isRequired" : false,
"id" : "1574250533840"
}
]
},
{
"attributeSetName" : "test-1",
"type" : "Product",
"id" : "1574158116355",
"updatedAt" : ISODate("2019-11-19T10:08:37.251Z"),
"createdAt" : ISODate("2019-11-19T10:08:37.251Z"),
"attributes" : []
}
]
}
I need to update element in attributes array and get the updates document into result object. This is what I tried so far.
const result = await this.model.findOneAndUpdate(
{
_id: settingsToBeUpdated._id,
"attributeSet": {
$elemMatch: {
"attributeSet.id": attributeSetId,
"attributes": {
$elemMatch: {
'attributes.id': id
}
}
}
}
},
{
$set: {
'attributeSet.$[outer].attributes.$[inner].attributeName': attributeDto.attributeName,
'attributeSet.$[outer].attributes.$[inner].defaultValue': attributeDto.defaultValue,
'attributeSet.$[outer].attributes.$[inner].isRequired': attributeDto.isRequired,
}
},
{
"arrayFilters": [
{ "outer.id": attributeSetId },
{ "inner.id": id }
]
}
);
It does not update the model. I refered to this link, but it does not help.
Any suggestion would be appreciated.

Couple of rectification required on the query, otherwise its almost there. The update is not working because $elemMatch for attributeSet (array of docs) field is to happen on id property of those docs to filter and not on attributeSet.id, it woudn't figure what it is. And nested elemMatch is not required, simply use dot notation.
To debug you can try it out with a find query.
Query (Shell):
db.collection.findOneAndUpdate(
{
_id: settingsToBeUpdated._id,
attributeSet: {
$elemMatch: {
id: attributeSetId,
"attributes.id": id
}
}
},
{
$set: {
"attributeSet.$[as].attributes.$[a].attributeName":
attributeDto.attributeName,
"attributeSet.$[as].attributes.$[a].defaultValue":
attributeDto.defaultValue,
"attributeSet.$[as].attributes.$[a].isRequired": attributeDto.isRequired
}
},
{
arrayFilters: [{ "as.id": attributeSetId }, { "a.id": id }],
returnNewDocument: true
}
);

Related

MongoDB: Filtering aggregation by field values and nested arrays

I built up a graph structure using MongoDB. I did some complex queries on this structure already, but I am struggling on selecting a subgraph of a given depth starting from a specific node via the aggregation pipeline.
I already did use the $graphLookup to get all the required nodes, which gives the following result:
{ "_id" : "O_4", "name" : "D", "type" : "Info", "links" : [ { "link" : "L_2", "objectId" : "O_1" }, { "link" : "L_4", "objectId" : "O_3" }, { "link" : "L_10", "objectId" : "O_6" } ] }
{ "_id" : "O_2", "name" : "B", "type" : "Info", "links" : [ { "link" : "L_1", "objectId" : "O_1" }, { "link" : "L_3", "objectId" : "O_3" } ] }
{ "_id" : "O_1", "name" : "A", "type" : "Info", "links" : [ { "link" : "L_1", "objectId" : "O_2" }, { "link" : "L_2", "objectId" : "O_4" } ] }
{ "_id" : "O_3", "name" : "C", "type" : "Info", "links" : [ { "link" : "L_3", "objectId" : "O_2" }, { "link" : "L_4", "objectId" : "O_4" }, { "link" : "L_5", "objectId" : "O_5" }, { "link" : "L_6", "objectId" : "O_7" } ] }
{ "_id" : "O_6", "name" : "F", "type" : "System", "links" : [ { "link" : "L_8", "objectId" : "O_7" }, { "link" : "L_9", "objectId" : "O_5" }, { "link" : "L_10", "objectId" : "O_4" } ] }
But now I want to remove the nested "link" objects (in array "links") where the "objectId" is not present in the above result, i.e. in "O_6" the link "L_8" should be removed, since the node "O_7" is not part of the subgraph.
I already tried playing around with $in, $facet and other stuff to get this problem solved, but it seems like I am unable ...
Maybe, you guys can help out?
Edit:
Just found a solution more or less - $filter does a decent job here:
{
$unwind: "$links"
}, {
$group: {
_id: null,
ids: {
$addToSet: "$_id"
},
links: {
$addToSet: "$links"
}
}
}, {
$project: {
links: {
$filter: {
input: "$links",
as: "link",
cond: {
$in: ["$$link.objectId", "$ids"]
}
}
}
}
}, {
$unwind: "$links"
}, {
$replaceRoot: {
newRoot: "$links"
}
}, {
$group: {
_id: "$link"
}
}
Returns what I needed - the list of Link-IDs:
{ "_id" : "L_1" }
{ "_id" : "L_10" }
{ "_id" : "L_3" }
{ "_id" : "L_2" }
{ "_id" : "L_4" }

how to push object except duplicate object in mongoDB

I want to add an object to an array.
But I want to remove duplicate objects.
here is mongoDB JSON
{
"_id" : ObjectId("5e61ef7e003307047c13329d"),
"subject" : "Grammar",
"deletedYn" : false,
"answerList" : [
{
"_id" : ObjectId("5e61ef8c003307047c1332a4"),
"sentence" : "hi gui, nice to meet you",
"answer" : [
{
"_id" : ObjectId("5e61f15cd863e104a8f66770"),
"word" : "nice",
"index" : 3
}
]
},
{
"_id" : ObjectId("5e61ef8c003307047c1332a5"),
"sentence" : "how to make a project.",
"answer" : []
}
],
"passageId" : "123",
}
So I want to push answer in answerList.
Below is the code I created, but it contains duplicate objects.
const update = await PassageGrammar.updateOne(
{
_id: passageGrammarId,
answerList: { $elemMatch: { _id: sentenceId } },
},
{
$addToSet: {
'answerList.$.answer': {
word: answerList.word,
index: answerList.index,
},
},
},
);
but It is not working (it has duplicate set) like that:
"answer" : [
{
"_id" : ObjectId("5e61f15cd863e104a8f66770"),
"word" : "nice",
"index" : 3
},
{
"_id" : ObjectId("5e61f15cd863e104a8f66770"),
"word" : "nice",
"index" : 3
}
]
How can I change it?
Try Creating a unique index each time and droping the duplicates if they exist
const update = await PassageGrammar.updateOne(
{
_id: passageGrammarId,
answerList: { $elemMatch: { _id: sentenceId } },
},
{
$addToSet: {
'answerList.$.answer': {
word: answerList.word,
index: answerList.index,
},
},
},
{ unique:true, dropDups:true }
);

How can I correctly output an array of objects in the reverse order from mongodb?

Comments are saved in an array of objects. How can I correctly output them in reverse order (comments from newest to oldest)?
My db:
{"_id":{"$oid":"5e3032f14b82d14604e7cfb7"},
"videoId":"zX6bZbsZ5sU",
"message":[
{"_id":{"$oid":"5e3032f14b82d14604e7cfb8"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"1"
},
{"_id":{"$oid":"5e3032f14b82d14604e7cfb9"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"2"
},
....
]
My sheme Mongoose:
const schema = new Schema({
videoId: { type: String, isRequired: true },
message: [
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
text: { type: String }
},
]
});
My code:
const userComments = await Comment.find(
{ videoId: req.query.videoId },
{ message: { $slice: [skip * SIZE_COMMENT, SIZE_COMMENT] } }
)
.sort({ message: -1 })
.populate('message.user', ['avatar', 'firstName']);
but sort not working;
thanks in advance!
You can simply use $reverseArray to reverse content of an array.
db.collection.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}
])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
}
]
}
Result :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
}
]
}
Your Query : You can use native MongoDB's $lookup instead of .populate() , So try below :
Comments.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}, {
$lookup: {
from: "User",
let: { ids: "$message.user" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$ids"] } }
},
{ $project: { avatar: 1, firstName: 1, _id: 0 } }
],
as: "userData"
}
}
])
You're going to want to use one of two things:
The MongoDB aggregation framework
Sorting within the application
If you choose to use the MongoDB aggregation framework, you'll likely want to use the $unwind operation to expand the array into separate documents, then sorting those documents with the $sort operation.
You can see more on how to do that in this ticket:
how to sort array inside collection record in mongoDB
You could also do this within your application by first executing a query, and then sorting each of the arrays in the result set.
Best,

How to take the duplicate records in mongodb

Here i have two document ,in this documents childNodes array ID is duplicate means , i want to take the userID and pedagogyID of the record,as per my documents second document under childNodes array 798 is coming duplicate, so i want to take the records
Documents
{
"userID" : "A",
"pedagogyID" : "100",
"summary" : {
"LearnProgress" : {
"childNodes" : [
{
"ID" : "123",
"status" : "in-progress"
},
{
"ID" : "456",
"status" : null
},
{
"ID" : "333",
"status" : null
}
],
}
}
}
{
"userID" : "B",
"pedagogyID" : "200",
"summary" : {
"LearnProgress" : {
"childNodes" : [
{
"ID" : "789",
"status" : "in-progress"
},
{
"ID" : "1010",
"status" : null
},
{
"ID" : "789",
"status" : null
}
],
}
}
}
Expected Output
{
"userID" : "B",
"pedagogyID" : "200",
}
MY Code
db.collectionname.aggregate(
[
{"$unwind":"$summary.LearnProgress.childNodes"},
{"$group":{
"_id":{"_id":"$_id","ID":"$summary.LearnProgress.childNodes.ID"},
"userID":{"$first":"$userID"},
"pedagogyID":{"$first":"$pedagogyID"},
"count":{"$sum":1}
}},
{"$match":{"count":{"$gt":1}}},
{"$group":{"_id":{"userID":"$userID","pedagogyID":"$pedagogyID"}}},
{"$replaceRoot":{"newRoot":"$_id"}}
],
{ allowDiskUse:true }
)
You can use below aggregation.
db.colname.aggregate([
{"$unwind":"$summary.LearnProgress.childNodes"},
{"$group":{
"_id":{"_id":"$_id","ID":"$summary.LearnProgress.childNodes.ID"},
"userID":{"$first":"$userID"},
"pedagogyID":{"$first":"$pedagogyID"},
"count":{"$sum":1}
}},
{"$match":{"count":{"$gt":1}}},
{"$group":{"_id":{"userID":"$userID","pedagogyID":"$pedagogyID"}}},
{"$replaceRoot":{"newRoot":"$_id"}}
],{"allowDiskUse":true})
db.collectionname.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path : "$summary.LearnProgress.childNodes",
}
},
// Stage 2
{
$group: {
_id:'$summary.LearnProgress.childNodes.ID',
count:{$sum:1},
pedagogyID:{$first:'$pedagogyID'},
userID:{$first:'$userID'}
}
},
// Stage 3
{
$match: {
count:{$gt:1}
}
},
// Stage 4
{
$project: {
userID:1,
pedagogyID:1,
_id:0
}
},
]
);

How do I return multiple fields in a MongoDB aggregate query?

I have a collection of MongoDB documents that look like this:
{
"_id" : "123",
"created_by": "bob",
"date_added": ISODate("2014-08-27T17:43:23Z"),
"size": "XL",
"color": "red"
}
The question I'm trying to answer is: What is the color of the item most recently added by each person?
I've gotten this far:
db.stuff.aggregate([
{ $group: { _id: { who: "$created_by"}, added: { $max: "$date_added" } } },
])
{ "_id" : { "who" : "bob" }, "added" : ISODate("2014-09-30T07:06:38.135Z") }
{ "_id" : { "who" : "mike" }, "added" : ISODate("2014-09-30T07:10:03.098Z") }
{ "_id" : { "who" : "mary" }, "added" : ISODate("2014-09-30T07:07:27.787Z") }
{ "_id" : { "who" : "john" }, "added" : ISODate("2014-09-30T07:09:51.418Z") }
However, it only returns the user's name and the date of when the document was added. I can't figure out how to get the query to also return the color. Thank you!
I think this will work for you.
db.stuff.aggregate([ {
$sort : {
date_added : -1
}
}, {
$group : {
_id : {
who : "$created_by"
},
added : {
$first : "$date_added"
},
color : {
$first : "$color"
}
}
} ]);