I have this data structure (classes with comments) and I want to add a 1-level deep reply. This means that I'd like to add another object on a "comentarios" element.
How can I achieve this with mongo?
This means: Match cursada (db) id, match clase (first array) id, match comment (second array) id, then add a new element there.
db.cursada.find({"_id": ObjectId("55444f56e5e154f7638b456a")}).pretty()
{
"_id" : ObjectId("55444f56e5e154f7638b456a"),
"clases" : [
{
"_id" : "554e7f2fe5e154797d8b4578",
"titulo" : "qewewqewq"
},
{
"_id" : "554e8be0e5e154dc698b4582",
"titulo" : "la mejor clase"
},
{
"_id" : "554eb90de5e154dd698b458b",
"comentarios" : [
{
"_id" : ObjectId("55a021afe5e154cf098b4567"),
"nombreUsuario" : "nombre",
"texto" : "432432423"
}
],
"titulo" : "Peeling - cosa"
},
{
"_id" : "554e91a0e5e154797d8b4587",
"titulo" : "fdsfdsa"
},
{
"_id" : "554f8f50e5e154dd698b458f",
"titulo" : "clase2"
},
{
"_id" : "554f99dae5e154797d8b45a7",
"titulo" : "con profesor"
},
{
"_id" : "554fa4a0e5e154797d8b45c4",
"titulo" : "profesor nombre nombre"
},
{
"_id" : "5557b37be5e154e07f8b4567",
"titulo" : "Dermatologia I"
},
{
"_id" : "5557c701e5e154066d8b456c",
"titulo" : "Acido hialuronico"
}
],
"curso" : "552fa5f1e5e1542e628b4567",
"fechaFin" : "2015-05-22T03:00:00.000Z",
"fechaIni" : "2015-05-08T03:00:00.000Z",
"titulo" : "cremotas"
}
Getting this result:
{
"_id" : ObjectId("55444f56e5e154f7638b456a"),
"clases" : [
{
"_id" : ObjectId("554eb90de5e154dd698b458b"),
"comentarios" : [
{
"_id" : ObjectId("55a021afe5e154cf098b4567"),
"nombreUsuario" : "nombre",
"texto" : "432432423",
----------------HERE
"replies": [
{ "_id": ....,
"user": ....,
"text":....,
"date":....
}]
----------------HERE
}
],
"titulo" : "Peeling - cosa"
},
]
}
One twisted example!
Luckily it presents no problem to MongoDB and its $elemMatch query operator:
Model.update({
_id: "55444f56e5e154f7638b456a",
classes: {
$elemMatch: {
_id: "554eb90de5e154dd698b458b",
}
}
}, {
$push: {
'classes.$.comentarios': {
nombreUsuario: 'New comment nombreUsuario',
texto: '111242515'
}
}
}, function(err, count) {
console.log('Updated ' + count + ' document');
});
What happens here?
First, we're specyfing specific course ("cursada") to update:
_id: "55444f56e5e154f7638b456a"
Then, using $elemMatch operator, we're restricting result of the query to contain only one class:
classes: {
$elemMatch: {
_id: "554eb90de5e154dd698b458b",
}
}
Now, having found specific class to update, we can finally add new comment to it. Here's our update query:
$push: {
'classes.$.comentarios': {
nombreUsuario: 'New comment nombreUsuario',
texto: '111242515'
}
}
$push operator tells MongoDB to add new comment to specified array of comments.
'classes.$.comentarios' specifies which nested comentarios array to update, using $ positional operator.
Finally, and this part should be self-explanatory: full object of new comment to add to the specified class.
It's also worth mentioning that if you're running into such deeply nested structure, perhaps it's worth thinking about spreading courses, classes, possibly even comments over separate MongoDB collections.
When nested documents are the way to go and when it's better to create separate collections can be actually tricky question to answer - here's nice presentation discussing this issue.
Related
Scenario
I've the following document from Chat collection with an array of messages and members in the chat.
And for each message, there will be status field which will store the delivered and read timestamp with respect to users.
{
"_id" : ObjectId("60679797b4365465745065b2"),
"members" : [
ObjectId("604e02033f4fc07b6b82771c"),
ObjectId("6056ef4630d7b103d8043abd"),
ObjectId("6031e3dce8934f11f8c9a79c")
],
"isGroup" : true,
"createdAt" : 1617401743720.0,
"updatedAt" : 1617436504453.0,
"messages" : [
{
"createdAt" : 1617401743719.0,
"updatedAt" : 1617401743719.0,
"_id" : ObjectId("60679797b4365465745065b3"),
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
"status" : []
}
]
}
So, I want to insert the following data, into messages.status array, to know when the message is received/read by the member.
{
receiverId: <member of chat>
deliveredAt: <timestamp>
readAt: <timestamp>
}
Question
How to write a query to insert the above json for each member (except the sender) in the status array by using the data from existing field?
So that, after query, the document should look like this:
{
"_id" : ObjectId("60679797b4365465745065b2"),
"members" : [
ObjectId("604e02033f4fc07b6b82771c"),
ObjectId("6056ef4630d7b103d8043abd"),
ObjectId("6031e3dce8934f11f8c9a79c")
],
"isGroup" : true,
"createdAt" : 1617401743720.0,
"updatedAt" : 1617436504453.0,
"messages" : [
{
"createdAt" : 1617401743719.0,
"updatedAt" : 1617401743719.0,
"_id" : ObjectId("60679797b4365465745065b3"),
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
"status" : [{
"receiverId": ObjectId("604e02033f4fc07b6b82771c")
"deliveredAt": <timestamp>
"readAt": <timestamp>
}, {
"receiverId": ObjectId("6056ef4630d7b103d8043abd")
"deliveredAt": <timestamp>
"readAt": <timestamp>
}]
}
]
}
Edit
I'm able to do this for static data.
Link: https://mongoplayground.net/p/LgVPfRoXL5p
For easy understanding: I've to map the members array and insert it into the status field of the messages
MongoDB Version: 4.0.5
You can use the $function operator to define custom functions to implement behavior not supported by the MongoDB Query Language. So along with updates-with-aggregate-pipeline and $function you can update messages.status array with only receiver's details as shown below:
NOTE: Works only with MongoDB version >= 4.4.
Try this:
let messageId = ObjectId("60679797b4365465745065b3");
db.chats.update(
{ "messages._id": messageId },
[
{
$set: {
"messages": {
$map: {
input: "$messages",
as: "message",
in: {
$cond: {
if: { $eq: ["$$message._id", messageId] },
then: {
$function: {
body: function (message, members) {
message.status = [];
for (let i = 0; i < members.length; i++) {
if (message.senderId.valueOf() != members[i].valueOf()) {
message.status.push({
receiverId: members[i],
deliveredAt: new Date().getTime(),
readAt: new Date().getTime()
})
}
}
return message;
},
args: ["$$message", "$members"],
lang: "js"
}
},
else: "$$message"
}
}
}
}
}
}
]
);
Output:
{
"_id" : ObjectId("60679797b4365465745065b2"),
"members" : [
ObjectId("604e02033f4fc07b6b82771c"),
ObjectId("6056ef4630d7b103d8043abd"),
ObjectId("6031e3dce8934f11f8c9a79c")
],
"isGroup" : true,
"createdAt" : 1617401743720,
"updatedAt" : 1617436504453,
"messages" : [
{
"_id" : ObjectId("60679797b4365465745065b3"),
"createdAt" : 1617401743719,
"updatedAt" : 1617401743719,
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
"status" : [
{
"receiverId" : ObjectId("604e02033f4fc07b6b82771c"),
"deliveredAt" : 1617625735318,
"readAt" : 1617625735318
},
{
"receiverId" : ObjectId("6056ef4630d7b103d8043abd"),
"deliveredAt" : 1617625735318,
"readAt" : 1617625735318
}
]
},
{
"_id" : ObjectId("60679797b4365465745065b4"),
"createdAt" : 1617401743719,
"updatedAt" : 1617401743719,
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79d"),
"status" : [ ]
}
]
}
Demo - https://mongoplayground.net/p/FoOvxXp6nji
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, e.g.
db.collection.update({
"messages.senderId": "6031e3dce8934f11f8c9a79c" // query
},
{
"$push": {
"messages.$[m].status": [ // push into the matching element of arrayFilters
{
"receiverId": ObjectId("604e02033f4fc07b6b82771c")
},
{
"receiverId": ObjectId("6056ef4630d7b103d8043abd")
}
]
}
},
{
arrayFilters: [
{
"m.senderId": "6031e3dce8934f11f8c9a79c" // matches array element where senderId is 6031e3dce8934f11f8c9a79c
}
]
})
Note- add index to messages.senderId for performance
We have scheme like this
{
"_id" : ObjectId("5e2ebceb2fb28c43520ef313"),
"attempts" : [
{
"ideas" : [
{
"ideaId" : ObjectId("5d314e8ade83a139bf31352a"),
"ratings" : [
{
"userId" : "20-432-3",
"points" : 3.0,
"isBest" : true
}
]
},
{
"ideaId" : ObjectId("5d314e8ade83a139bf31352c"),
"ratings" : [
{
"userId" : "20-432-2",
"points" : 3.0,
"isBest" : true
}
]
},
{
"ideaId" : ObjectId("5d314e8ade83a139bf31352e")
}
]
}
]
I need to to update document only if it does not have given userId in attempts.ideas.ratings. For example when user with userId with "20-432-3" want to update document, it will fail (because there is already rating from him in first idea), on the other hand user with userId "20-432-1" can update document.
it's like 3 nested scheme, my question is: is it possible in one query???
thank you in advance
EDIT.
Found solution. Obviously it was the easiest way...
_id: ObjectId("5e2ebceb2fb28c43520ef313"),
"attempts.ideas.ratings.uuIdentity": { $ne: "20-432-1" }
Hi I am developing MeteorJS app, I am stuck at updating a sub sub array element.
It is a poll application and I have the following database structure:
Under each question there are options and when a user clicks a button of an option, I want to increment that options votes by one and every user should have one vote right for each question.
From the button, I am passing name and questionId data in order to find the right option to increment vote. I should find the specific question with the questionId and then the specific array with the name under the Options.
Where I am stuck at is I can't find it.
Please help, thanks
Collection Name: Polls
Each Poll has the following structure:
{
"_id" : "uJtBt8mM2pbTYfwND",
"createdAt" : ISODate("2017-04-03T22:40:14.678Z"),
"pollName" : "First Poll",
"entryOwner" : "gdAHxDrxFuTvYiFt8",
"question" : [
{
"name" : "Question number 1",
"questionId" : "xgYQxGxpwBXaQpjXN",
"options" : [
{
"name" : "John",
"votes" : 0
},
{
"name" : "Adam",
"votes" : 0
},
{
"name" : "Robert",
"votes" : 0
}
]
},
{
"name" : "Question number 2",
"questionId" : "zviwYHHsaATBdG6Jw",
"options" : [
{
"name" : "John",
"votes" : 0
},
{
"name" : "Adam",
"votes" : 0
},
{
"name" : "Robert",
"votes" : 0
}
]
}
],
}
You can use $and which performs a logical AND operation on an array of two or more expressions.
{ $and: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }
The first expression here would be to get the question with questionId.
'question.questionId': "xgYQxGxpwBXaQpjXN"
And second expression to specify the object with matching name in options array.
To find the object with the name in options array, you can use $elemMatch which allows to specify queries.
{ <field>: { $elemMatch: { <query1>, <query2>, ... } } }
To get the object in options array having name as "John".
'question.options': {
$elemMatch: {
name: "John"
}
}
And finally, use $inc to increase the votes (here by 1).
It will get the first matching element (with $).
'question.options.$.votes': 1
Here's the full code:
db.Polls.update({
$and: [{
'question.questionId': "xgYQxGxpwBXaQpjXN"
},
{
'question.options': {
$elemMatch: {
name: "John"
}
}
}
]
}, {
$inc: {
'question.options.$.votes': 1
}
})
This question already has answers here:
How to Update Multiple Array Elements in mongodb
(16 answers)
Closed 6 years ago.
Hi i am new in nodejs i need to update a value in nested array using _id of document my database document is look like this..
"complaints" : [
{
"complaint" : "head light is not working",
"complaintid" : ObjectId("57205219a56d2b8c0f9274a4"),
"_id" : ObjectId("57454c9249218eb40c1c0d1f"),
"labour" : 350,
"partCost" : 0,
"part" : [
{
"id" : ObjectId("56f12eaab915bd9800272ed7"),
"estimate" : 450,
"partname" : "clutch",
"_id" : ObjectId("57454cef49218eb40c1c0d25"),
"quantity" : 0,
"qrcodes" : []
},
{
"id" : ObjectId("56f12eaab915bd9800272ed7"),
"estimate" : 450,
"partname" : "rear tyre",
"_id" : ObjectId("57454cef49218eb40c1c0d24"),
"quantity" : 0,
"qrcodes" : []
}
],
"acceptance" : true,
"inspection" : false,
"color" : "#8929A9",
"status" : "APPROVED",
"estimate" : 1200,
"solution" : "HEAD LIGHT CHANGE",
"problems" : "HEAD LIGHT IS NOT WORKING"
},
i need to update quantity value of part array exist inside the part array using _id of part array
i am trying this but its not working what should i do for update this value...
var partdata = req.payload.parts;
for(var k = 0; k< partdata.length ; k++ ){
CPS.update({
'complaints.part._id' : partdata[k].partid
}, {
"$inc" : {
'complaints.part.$.quantity' : partdata[k].quantity
}
}).exec
(function(err,temp) {
if(err){
res(err).code(500);
}else{
console.log(temp);
}
});
}
MongoDB doesn't support matching into more than one level of an array.
Consider altering your document model so each document represents an
operation, with information common to a set of operations duplicated
in the operation documents.
Following is not the solution for your case.
But in-case you know the index then you could do something like this:
Assume a sample document like:
{
"_id" : ObjectId("57454c9249218eb40c1c0d1f"),
"part" : [{ "quantity" : 111 }, { "quantity" : 222 }]
}
Then this query should work.
db.test.update({ "_id" : ObjectId("57454c9249218eb40c1c0d1f") }, { "$set" : { "part.1.quantity" : 999 } })
Document will get modified as follows :
{
"_id" : ObjectId("57454c9249218eb40c1c0d1f"),
"array" : [{ "quantity" : 222 }, { "quantity" : 999 }]
}
Update: You can try following way of doing the update. But its not recommended way of doing probably you need to restructure your schema.
db.test.aggregate([
{ "$unwind": "$complaints" },
{ "$unwind": "$complaints.part" },
{ "$project":
{
_id: "$complaints.part._id",
partqty: "$complaints.part.quantity"
}
},
]);
This should return as follows:
{
"_id" : ObjectId("57454cef49218eb40c1c0d25"),
"partqty" : 111
}
{
"_id" : ObjectId("57454cef49218eb40c1c0d24"),
"partqty" : 222
}
Now you can use this information to update, e.g
var cur = db.test.aggregate([
{ "$unwind": "$complaints" },
{ "$unwind": "$complaints.part" },
{ "$project":
{
_id: "$complaints.part._id",
partqty: "$complaints.part.quantity"
}
},
]);
while (cur.hasNext()) {
var doc = cur.next();
//Note the index should be know again :|
db.test.update({ "complaints.part._id": ObjectId("57454cef49218eb40c1c0d25") },
{ "$set": { "complaints.$.part.1.quantity": 55 }},
{ "multi": true})
}
I would like to add a value to an a field which lies within an array element, I would also like the amount of elements in the array to be returned and also that only one value is allowed to be input. below is the data:
{
"_id" : ObjectId("56bb59beb32fa53064f51e3f"),
"title" : "okok",
"views" : 1,
"messages" : [
{
"authorId" : ObjectId("56bb599e8f308f1664c93011"),
"upvotes" : [], // <--- want to push a value here
"created" : ISODate("2016-02-10T15:39:42.006Z"),
"updated" : ISODate("2016-02-10T15:39:42.006Z"),
"message" : "okok"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93010"),
"upvotes" : [], // <--- insert into here plz
"created" : ISODate("2016-02-10T15:39:47.170Z"),
"updated" : ISODate("2016-02-10T15:39:47.170Z"),
"message" : "uhuhuh"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93011"),
"upvotes" : [],
"created" : ISODate("2016-02-10T15:40:01.772Z"),
"updated" : ISODate("2016-02-10T15:40:01.772Z"),
"message" : "åpåpå"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93010"),
"upvotes" : [],
"created" : ISODate("2016-02-10T15:40:04.889Z"),
"updated" : ISODate("2016-02-10T15:40:04.889Z"),
"message" : "påpåpå<br /><br />påå"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93010"),
"upvotes" : [],
"created" : ISODate("2016-02-11T12:36:26.006Z"),
"updated" : ISODate("2016-02-11T12:36:26.006Z"),
"message" : "testt"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93013"),
"upvotes" : [],
"created" : ISODate("2016-02-11T12:36:31.514Z"),
"updated" : ISODate("2016-02-11T12:36:31.514Z"),
"message" : "tetetet"
}
]
}
I have the document _id and the array element created field to locate the element in the array.
I tried the following:
}
$match: {
_id: retard.ObjectId(data.id),
'messages.created': data.created
}
}, {
$group: {
'messages.$.upvotes': {
$addToSet: 'myTestValueToInsert'
}
}
}, {
$project: {
numUpvotes: {
$size: '$upvotes'
}
}
}, {
multi: false
}
However getting an error. I've been reading the aggregation documentation but the examples are slightly lacking!
The aggregation framework is not intended to and cannot be used to insert or update documents(this is an extended capability and not an exception), rather, it can process data records and return computed results.
You could use the update query to update matching sub document:
var myTestValueToInsert = 1;
db.t.update({
"_id":retard.ObjectId(data.id),
"messages.created":data.created
},
{
"$addToSet":{"messages.$.upvotes":myTestValueToInsert}
})
Then, to retrieve the size of the modified array, you could aggregate it as below:
db.t.aggregate([
{$match:{"_id":retard.ObjectId(data.id)}},
{$unwind:"$messages"},
{$match:{"messages.created":data.created}},
{$project:{"size":{$size:"$messages.upvotes"},"_id":0}}
])
If you are using the shell, there is a nice wrapper to update and get the updated document, but you would need to get the size of the array inside the document in the client side:
db.t.findAndModify({
"query":{"_id":retard.ObjectId(data.id),"messages.created":data.created},
"update":{$addToSet:{"messages.$.upvotes":myTestValueToInsert}},
"new":true
})