Find and replace a sub sub array element in MongoDB - arrays

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
}
})

Related

Update array at specific index by other filed in MongoDB

I have a collection, consist of name and data.
data is an array with 2 elements, each element is the object with code and qty.
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"code" : "a",
"qty" : 3
},
{
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"code" : "c",
"qty" : 5
},
{
"code" : "d",
"qty" : 6
}
]
}
I want to update the code of the first element to name of its document. The result I want is
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"code" : "Anna",
"qty" : 3
},
{
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"code" : "James",
"qty" : 5
},
{
"code" : "d",
"qty" : 6
}
]
}
I just google to find how to:
update array at a specific index (https://stackoverflow.com/a/34177929/11738185)
db.Collection.updateMany(
{ },
{
$set:{
'data.0.code': '$name'
}
}
)
But the code of the first element in data array is a string '$name', not a value (Anna, James)
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"code" : "$name",
"qty" : 3
},
{
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"code" : "$name",
"qty" : 5
},
{
"code" : "d",
"qty" : 6
}
]
}
update a field by the value of another field. It takes me to use pipeline updating (https://stackoverflow.com/a/37280419/11738185): the second param of updateMany is array (pipeline)
db.Collection.updateMany(
{ },
[{
$set:{
'data.0.code': '$name'
}
}]
)
and It adds field 0 to each element in data array
{
"_id" : ObjectId("605c666a15d2612ed0afedd2"),
"name" : "Anna",
"data" : [
{
"0" : {
"code" : "Anna"
},
"code" : "a",
"qty" : 3
},
{
"0" : {
"code" : "Anna"
},
"code" : "b",
"qty" : 4
}
]
},
{
"_id" : ObjectId("605c666a15d2612ed0afedd3"),
"name" : "James",
"data" : [
{
"0" : {
"code" : "James"
},
"code" : "c",
"qty" : 5
},
{
"0" : {
"code" : "James"
},
"code" : "d",
"qty" : 6
}
]
}
I can't find the solution for this case. Could anyone to help me? How can I update array at fixed index by other field. Thanks for reading!
1. update array at a specific index
You can't use internal fields as value of another fields, it will work only when you have external value to update like { $set: { "data.0.code": "Anna" } }.
2. update a field by the value of another field
Update with Aggregation pipeline can't allow to access data.0.code syntax.
You can try using $reduce in update with aggregation pipeline,
$reduce to iterate loop of data array, set empty array in initialValue of reduce, Check condition if initialValue array size is zero then replace code with name and merge with current object using $mergeObjects, else return current object,
$concatArrays to concat current object with initialValue array
db.collection.update({},
[{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
$cond: [
{ $eq: [{ $size: "$$value" }, 0] },
{ $mergeObjects: ["$$this", { code: "$name" }] },
"$$this"
]
}
]
]
}
}
}
}
}],
{ multi: true }
)
Playground
I think easier would be another way.
Just save the model before and use it for updating after
var annaModel = nameModel.findOne({_id: "605c666a15d2612ed0afedd2" })
nameModel.findOneAndUpdate({_id: "605c666a15d2612ed0afedd2"},{$set:{'data.0.code': annaModel.name}})

MongoDB not in any array (nested arrays)

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" }

Adding object to array in array in MongoDB

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.

MongoDB Update Array element

I have a document structure like
{
"_id" : ObjectId("52263922f5ebf05115bf550e"),
"Fields" : [
{
"Field" : "Lot No",
"Rules" : [ ]
},
{
"Field" : "RMA No",
"Rules" : [ ]
}
]
}
I have tried to update by using the following code to push into the Rules Array which will hold objects.
db.test.update({
"Fields.Field":{$in:["Lot No"]}
}, {
$addToSet: {
"Fields.Field.$.Rules": {
"item_name": "my_item_two",
"price": 1
}
}
}, false, true);
But I get the following error:
can't append to array using string field name [Field]
How do I do the update?
You gone too deep with that wildcard $. You match for an item in the Fields array, so you get a access on that, with: Fields.$. This expression returns the first match in your Fields array, so you reach its fields by Fields.$.Field or Fields.$.Result.
Now, lets update the update:
db.test.update({
"Fields.Field": "Lot No"
}, {
$addToSet: {
"Fields.$.Rules": {
'item_name': "my_item_two",
'price':1
}
}
}, false, true);
Please note that I've shortened the query as it is equal to your expression.

Update array value vs object value

How do you update the sku value within an array as in example "B". Should I go with A over B?
Option A - Object
Scheme
"data" : {
"products" : {
235099432:{
"product_id" : 101242538,
"sku" : "",
"variant_id" : 235099432
},
]
}
Update
db.col.update({
"data.products.235099432.variant_id": 235099432
}, {
$set: {
"data.products.235099432.sku": "ITM-RED-212"
}
});
Option B - Array
Scheme
"data" : {
"products" : [
{
"product_id" : 101242538,
"sku" : "",
"variant_id" : 235099432
},
]
}
You can use the $ position operator to identify the products array element to update that matches your filter:
db.col.update({
"data.products.variant_id": 235099432
}, {
$set: {
"data.products.$.sku": "ITM-RED-212"
}
});
My vote would be option B; the use of dynamic keys in option A can get very messy.

Resources