Modify a document inside an array in MongoDB - mongodb

Past answers (from mid 2013 and before) don't seem to work and links to the documentation are all out of date.
Example user object:
{
"name": "Joe Bloggs",
"email": "joebloggs#example.com",
"workstations" : [
{ "number" : "10001",
"nickname" : "home" },
{ "number" : "10002",
"nickname" : "work" },
{ "number" : "10003",
"nickname" : "vacation" }
]
}
How can I modify the nickname of a workstation?
I tried using $set, workstations.$ and workstations.nickname but none gave the desired results.

Short answer, you have to use array index. For example, you want to update the nickname of 10002: {$set:{"workstations.1.nickname":"newnickname"}}
Here is the complete example:
> db.test.update({"_id" : ObjectId("5332b7cf4761549fb7e1e72f")},{$set:{"workstations.1.nickname":"newnickname"}})
> db.test.findOne()
{
"_id" : ObjectId("5332b7cf4761549fb7e1e72f"),
"email" : "joebloggs#example.com",
"name" : "Joe Bloggs",
"workstations" : [
{
"number" : "10001",
"nickname" : "home"
},
{
"nickname" : "newnickname",
"number" : "10002"
},
{
"number" : "10003",
"nickname" : "vacation"
}
]
}
>
If you don't know the index (position of the workstations), you can update the doc using $elemMatch:
>db.test.update(
{
"email": "joebloggs#example.com",
"workstations": { "$elemMatch" { "number" : "10002" } }
},
{
"$set": { "workstations.$.nickname": "newnickname2" }
}
)
>

#naimdjon's answer would work. To generalize, you could use the $elemMatch operator in combination with the $ positional operator to update one element in the array using below query:
db.test.update({
// Find the document where name="Joe Bloggs" and the element in the workstations array where number = "10002"
"name": "Joe Bloggs",
"workstations":{$elemMatch:{"number":"10002"}}
},
{
// Update the nickname in the element matched
$set:{"workstations.$.nickname":"newnickname"}
})
Note: $elemMatch is only required if you need to match more than one component in the array. If you are going to match on just the number, you could use "workstations.number":"10002"

As long as you know "which" entry you wish to update then the positional $ operator can be of help. But you need to update your query form:
db.collection.update(
{
"email": "joebloggs#example.com",
"workstations": { "$elemMatch" { "nickname" : "work" } }
},
{
"$set": { "workstations.$.nickname": "new name" }
}
)
So that is the general form. What you need to do here is "match" something in the array in order to get a "position" to use for the update.
Alternately, where you know the position, then you can just "specify" the position with "dot notation":
db.collection.update(
{
"email": "joebloggs#example.com",
},
{
"$set": { "workstations.1.nickname": "new name" }
}
)
Which updates the second element in the array, and does not need the "matching" part in the query.

Related

How to update Meteor array element inside a document

I have a Meteor Mongo document as shown below
{
"_id" : "zFndWBZTvZPgSKXHP",
"activityId" : "aRDABihAYFoAW7jbC",
"activityTitle" : "Test Mongo Document",
"users" : [
{
"id" : "b1#gmail.com",
"type" : "free"
},
{
"id" : "JqKvymryNaCjjKrAR",
"type" : "free"
},
],
}
I want to update a specific array element's email with custom generated id using Meteor query something like the below.
for instance, I want to update the document
if 'users.id' == "b1#gmail.com" then update it to users.id = 'SomeIDXXX'
So updated document should looks like below.
{
"_id" : "zFndWBZTvZPgSKXHP",
"activityId" : "aRDABihAYFoAW7jbC",
"activityTitle" : "Test Mongo Document",
"users" : [
{
"id" : "SomeIDXXX",
"type" : "free"
},
{
"id" : "JqKvymryNaCjjKrAR",
"type" : "free"
},
],
}
I have tried the below but didnt work.
Divisions.update(
{ activityId: activityId, "users.id": emailId },
{ $set: { "users": { id: _id } } }
);
Can someone help me with the relevant Meteor query ? Thanks !
Your query is actually almost right except for a small part where we want to identify the element to be updated by its index.
Divisions.update({
"activityId": "aRDABihAYFoAW7jbC",
"users.id": "b1#gmail.com"
}, {
$set: {"users.$.id": "b2#gmail.com"}
})
You might need the arrayFilters option.
Divisions.update(
{ activityId: activityId },
{ $set: { "users.$[elem].id": "SomeIDXXX" } },
{ arrayFilters: [ { "elem.id": "b1#gmail.com" } ], multi: true }
);
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/
You need to use the $push operator instead of $set.
{ $push: { <field1>: <value1>, ... } }

Mongo remove from nested object by value

I have a Mongo collection the consists of a document and a nested object describing what collections the document is in and when it was added. I would like to remove key-value pairs from a nested object based on a condition, e.g. is the value (a date) before 1-1-2016.
Example:
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : {
"c01" : ISODate("2016-10-27T15:52:04.512Z"),
"c02" : ISODate("2015-11-21T16:06:06.546Z")
}
}
needs to become
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : {
"c01" : ISODate("2016-10-27T15:52:04.512Z"),
}
}
One alternative would be to change the schema to something like this:
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : [
{
"id": "c01",
"date": ISODate("2016-10-27T15:52:04.512Z")
},
{
"id": "c02",
"date" : ISODate("2015-11-21T16:06:06.546Z")
}
]
}
in which case removing a document from a would be easy. I am a bit reluctant to do that because it would complicate some of the other queries I would like to support. Thanks!
I prefer the second structure for your schema
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : [
{
"id": "c01",
"date": ISODate("2016-10-27T15:52:04.512Z")
},
{
"id": "c02",
"date" : ISODate("2015-11-21T16:06:06.546Z")
}
]
}
then able to remove from collections like this
db.collectionName.update(
{ },// if you want can add query for specific Id {"_id" : requestId},
{ $pull: { collections: { date: {$lt: yourDate} } } }, // if need can convert iso date string like: new Date(yourDate).toISOString()
{ multi: true }
)

Retrieving value of an emedded object in mongo

Followup Question
Thanks #4J41 for your spot on resolution. Along the same lines, I'd also like to validate one other thing.
I have a mongo document that contains an array of Strings, and I need to convert this particular array of strings into an array of object containing a key-value pair. Below is my curent appraoch to it.
Mongo Record:
Same mongo record in my initial question below.
Current Query:
templateAttributes.find({platform:"V1"}).map(function(c){
//instantiate a new array
var optionsArray = [];
for (var i=0;i< c['available']['Community']['attributes']['type']['values'].length; i++){
optionsArray[i] = {}; // creates a new object
optionsArray[i].label = c['available']['Community']['attributes']['type']['values'][i];
optionsArray[i].value = c['available']['Community']['attributes']['type']['values'][i];
}
return optionsArray;
})[0];
Result:
[{label:"well-known", value:"well-known"},
{label:"simple", value:"simple"},
{label:"complex", value:"complex"}]
Is my approach efficient enough, or is there a way to optimize the above query to get the same desired result?
Initial Question
I have a mongo document like below:
{
"_id" : ObjectId("57e3720836e36f63695a2ef2"),
"platform" : "A1",
"available" : {
"Community" : {
"attributes" : {
"type" : {
"values" : [
"well-known",
"simple",
"complex"
],
"defaultValue" : "well-known"
},
[......]
}
I'm trying to query the DB and retrieve only the value of defaultValue field.
I tried:
db.templateAttributes.find(
{ platform: "A1" },
{ "available.Community.attributes.type.defaultValue": 1 }
)
as well as
db.templateAttributes.findOne(
{ platform: "A1" },
{ "available.Community.attributes.type.defaultValue": 1 }
)
But they both seem to retrieve the entire object hirarchy like below:
{
"_id" : ObjectId("57e3720836e36f63695a2ef2"),
"available" : {
"Community" : {
"attributes" : {
"type" : {
"defaultValue" : "well-known"
}
}
}
}
}
The only way I could get it to work was with find and map function, but it seems to be convoluted a bit.
Does anyone have a simpler way to get this result?
db.templateAttributes.find(
{ platform: "A1" },
{ "available.Community.attributes.type.defaultValue": 1 }
).map(function(c){
return c['available']['Community']['attributes']['type']['defaultValue']
})[0]
Output
well-known
You could try the following.
Using find:
db.templateAttributes.find({ platform: "A1" }, { "available.Community.attributes.type.defaultValue": 1 }).toArray()[0]['available']['Community']['attributes']['type']['defaultValue']
Using findOne:
db.templateAttributes.findOne({ platform: "A1" }, { "available.Community.attributes.type.defaultValue": 1 })['available']['Community']['attributes']['type']['defaultValue']
Using aggregation:
db.templateAttributes.aggregate([
{"$match":{platform:"A1"}},
{"$project": {_id:0, default:"$available.Community.attributes.type.defaultValue"}}
]).toArray()[0].default
Output:
well-known
Edit: Answering the updated question: Please use aggregation here.
db.templateAttributes.aggregate([
{"$match":{platform:"A1"}}, {"$unwind": "$available.Community.attributes.type.values"},
{$group: {"_id": null, "val":{"$push":{label:"$available.Community.attributes.type.values",
value:"$available.Community.attributes.type.values"}}}}
]).toArray()[0].val
Output:
[
{
"label" : "well-known",
"value" : "well-known"
},
{
"label" : "simple",
"value" : "simple"
},
{
"label" : "complex",
"value" : "complex"
}
]

Select LIKE string in MongoDB with Array

I have the following document with this array:
"r" : [{
"id" : "890",
"ca" : "Other CPF Schemes and Priorities",
"su" : "National Day Rally 2015"
}, {
"id" : "1031-52347",
"ca" : "Current Events",
"su" : "Lee Kuan Yew"
}]
and I would like to list all documents where the id has got a dash so document with "id" : "1031-52347" will be returned.
I tried this:
{
r: { id: { $in: [/^-/] } }
}
but not able to get anything.
What would be the correct syntax?
I used this regex:
^[0-9]+-[0-9]+$
Debuggex Demo
You should try this database query:
"r":
{
{ "id": {"$regex" : new RegExp("^[0-9]+-[0-9]+$") } }
}
UPDATE
Working database queries by Blakes Seven
db.mydb.find({ "r.id": { "$regex": "^[0-9]+-[0-9]+$" }})
or
db.mydb.find({ "r.id": /^[0-9]+-[0-9]+$/ })

get a mongo document based in two different values

I have the following document structure
{
"_id" : "aaa0001",
"path" : "/some/path",
"information" : {
"name" : "info"
},
"colors" : {
"colors" : [
{
"key" : "AAAA001",
"name" : "White"
},
{
"key" : "BBBB002",
"name" : "Black"
}
]
}
}
the idea is that I have to return the document by the color key. I have two parameters the "path" and the "color", so, I was trying to make something like this
db.components.find(
{$and:[
{"path" : "/some/path"},
{"colors":{"colors" : {$elemMatch: { "key" : "AAAA001" } } } }
]})
I'm getting the following message "Script is executed successfully, but there is no results to show".
Can anyone give me some directions regarding this?
thanks
Use the following query:
db.components.find({
"path": "/some/path",
"colors.colors.key" : "AAAA001"
})
MongoDB expects query document to contain field-value pairs { <field>: <value> }. So, in your example you're querying for a document with colors field equal to:
{"colors" : {$elemMatch: { "key" : "AAAA001" } } }
As for $and and $elemMatch operators, you don't need them in such a simple query.
For more information read Query Documents.
Update
You can also select only matching subdocument from colors array using Positional Operator $:
db.components.find({
"path": "/some/path",
"colors.colors.key" : "AAAA001"
}, {
_id: 0,
"colors.colors.$": 1
})
Though, you won't be able to change your documents structure, thus getting
{ "colors" : { "colors" : [ { "key" : "AAAA001", "name" : "White" } ] } }