Use two push-es in update command - mongodb

Well the answer is probably no but I am curious to ask.
I have a Document which has two level of arrays in it:
{ '_id : '...' , events : [ urls : [], social_id : [] ], 'other_data' : '...' }
The code below works. What is does is update on a specific event the url array and adds to that set the event['event_url'] value (python).
db.col.update(
{ 'f_id' : venue['id'],
"events.title" : find_dict["events.title"] },
{ '$addToSet': { 'events.$.urls': event['event_url']} }
)
However in the same event I want to add a social id if not exists.
db.col.update(
{ 'f_id' : venue['id'],
"events.title" : find_dict["events.title"] },
{ '$addToSet': { 'events.$.social_id': event['social_id']} }
)
I was wandering if it's possible to merge the above commands into one and not run the update twice. I have not found anything in the documentation but I guess it's worth asking.

You can combine the two updates into a single operation by including both fields in the $addToSet object:
db.col.update(
{ 'f_id': venue['id'], "events.title": find_dict["events.title"] },
{ '$addToSet': {
'events.$.urls.': event['event_url'],
'events.$.social_id.': event['social_id']
}}
)

Related

How to update nested object in mongodb

I am rather new to mongodb and self learning. Since recently I am a bit stuck on this one. I would like to update a nested document. I worked around it by retrieving the full document and updating the entire thing, but I need a solution where I can update in a single shot. I made the following simplified example:
db.test.insert({
'name': 'someSchool',
'classes': [
{
'name': 'someClass',
'students': [
{
'name': 'Jack',
'coolness' : 7
}
]
}
]
});
Here the arrays only have one entry for simplicity, you understand we normally have more...
db.test.find({
'name' : 'someSchool',
'classes' : {
$elemMatch: {
'name': 'someClass',
'students.name' : 'Jack'
}
}
});
This find works! Thats great, so now I want to use it in an update, which is where Im stuck. Something like this??
db.test.update({
'name' : 'someSchool',
'classes' : {
$elemMatch: {
'name': 'someClass',
'students.name' : 'Jack'
}
}
}, {
'$set' : { 'classes.students.coolness' : 9}
});
So this doesnt work and now I am stuck at it :) Hopefully someone here can help.
Side note: I am using mongoose. If you guys tell me its better to change the schema to make it easier somehow, I can.
Update since marked as duplicate: In the referenced question, there is only 1 level of nesting and the problem gets solved by using dot notation. Here there are 2 levels. I accepted teh answer which explains it is not supported and which gives a correct workaround.
As mentioned here it's not possible right now to update two level nested arrays.
I would suggest you to change your schema in order to replace one level array nesting with object. So your document would look like this:
{
"name" : "someSchool",
"classes" : {
"someClass" : {
"students" : [
{
"name" : "Jack",
"coolness" : 7
}
]
}
}
}
Then you can use positional operator to update specific array element. But be aware that positional operator updates only the first element in array that matches the query.
Update query would look like this:
db.test.update({
'classes.someClass.students' : {
$elemMatch: {
'name' : 'Jack'
}
}
}, {
'$set' : {
'classes.someClass.students.$.coolness' : 9
}
});

db.collection.find returns multiple records after addToSet

I use addToSet command (to add UpdatedData):
MyTable.update({ "UrlLink": "http://www.someurl.com"}, {
$addToSet: {
UpdatedData: {
TheDate: ThisDate,
NewInfo: ThisInfo
}
}
},
function (err, result) {
next(err,result)
}
);
But then, when I do the query (after UpdatedData is added to my document), I see that it returns two documents. Instead of only one updated document:
db.MyTable.find( {"UrlLink": "http://www.someurl.com"})
{ "_id" : ObjectId("56485922143a886f66c2f8bb"), "UrlLink" : "http://www.someurl.com", "Stuff" : "One", "UpdatedData" : [ { "TheDate" : "11/15/2015", "NewInfo" : "Info1", "_id" : ObjectId("5648599a71efc79c660f76d3") } ] }
{ "_id" : ObjectId("5648599f71efc79c660f76d4"), "UrlLink" : "http://www.someurl.com", "Stuff": "One", "UpdatedData" : [ ] }
So it seems that addToSet creates a new document with new _id, instead of updating the old record (ObjectId("5648599f71efc79c660f76d4")). But I only see the updated document in robomongo (ObjectId("56485922143a886f66c2f8bb")). Any ideas why this happens and how I could prevent that behaviour?
update cannot create a new document, it can only update existing.
This looks like you have created two documents with the same url.. Then when you update it just updates the first one..
To prevent the creation of a document with an already existent url you can create an index and set it to unique
db.collection.createIndex({ UrlLink: 1 }, { unique: true })
This will prevent creation of new documents with the same url, and it will also make queries by UrlLink as fast as possible.

Update a Map value document value using Mongo

I have a document like myPortCollection
{
"_id" : ObjectId("55efce10f027b1ca77deffaa"),
"_class" : "myTest",
"deviceIp" : "10.115.75.77",
"ports" : {
"1/1/x1" : {
"portId" : "1/1/x1",
healthState":"Green"
I tried to update
db.myPortCollection.update({
{ deviceIp:"10.115.75.77"},
{ "chassis.ports.1/1/x10.rtId":"1/1/x10" },
{ $set: { "chassis.ports.1/1/x10.healthState" : "Red" }
})
But I am getting error that attribute names mentioned is wrong,.Please help in specifying the syntax properly for embedded map document update.
The "query" portion is wrong as you have split conditions into two documents. It should be this:
db.myPortCollection.update(
{
"deviceIp":"10.115.75.77",
"chassis.ports.1/1/x10.rtId":"1/1/x10"
},
{ "$set": { "chassis.ports.1/1/x10.healthState" : "Red" } }
)
And as long as the query then matches ( valid data not shown in your question ) then the specified field will be set or added.

Script to add one value to array in mongo collection

/* 0 */
{
"_id" : ObjectId("55addc2f8dab32aca87ce0bd"),
"partNum" : "part1",
"dest" : "First Part",
"sales" : [
"sale1",
"sale2",
"sale3"
],
"salesData" : {
"sale1" : {
"mcode" : "mc11",
"dtype" : [
"AAA",
"BBB"
]
}
}
}
/* 1 */
{
"_id" : ObjectId("55addc408dab32aca87ce0be"),
"partNum" : "part2",
"dest" : "Second Part",
"sales" : [
"sale1",
"sale2",
"sale3"
],
"salesData" : {
"sale1" : {
"mcode" : "mc22",
"dtype" : [
"AAA",
"BBB"
]
}
}
}
I am not that much efficient in writing mongo script. My requirement is to append one more value to "dtype" array wherever "mcode" is "mc11" in all of the documents inside the collection. Above is the two document output from my collection. I was using the below script to do it and its not working. Can anyone please help me
db.testingRD.find().forEach( function(myDocument)
{
db.testingRD.update({id: myDocument._id}, {$push : {"salesData.sale1.dtype" : "DDD"}});
});
To append one more value to "dtype" array wherever "mcode" is "mc11", use the following update where the query object is the selection criteria for the update and is the same query selector as in the find() method, the update object has the $push modifications to apply and then the options document which is optional. If that is set to true, it updates multiple documents that meet the query criteria:
var query = { "salesData.sale1.mcode": "mc11" },
update = {
"$push": { "salesData.sale1.dtype": "DDD" }
},
options = { "multi": true };
db.testingRD.update(query, update, options);
You had a typing mistake in the script (you forgot an underscore):
db.testingRD.find().forEach( function(myDocument)
{
db.testingRD.update({_id: myDocument._id}, {$push : {"salesData.sale1.dtype" : "DDD"}});
});
I always use a trick when an update seams to not working: I change the update with a printjson + find so that I can see if it is matching anything:
db.testingRD.find().forEach( function(myDocument) { printjson(db.testingRD.find({_id: myDocument._id})) } );

Mongodb MapReduce for grouping up to n per category using Mongoid

I have a weird problem with MongoDB (2.0.2) map reduce.
So, the story goes like this:
I have Ad model (look for model source extract below) and I need to group up to n ads per category in order to have a nice ordered listing I can later use to do more interesting things.
# encoding: utf-8
class Ad
include Mongoid::Document
cache
include Mongoid::Timestamps
field :title
field :slug, :unique => true
def self.aggregate_latest_active_per_category
map = "function () {
emit( this.category, { id: this._id });
}"
reduce = "function ( key, value ) {
return { ads:v };
}"
self.collection.map_reduce(map, reduce, { :out => "categories"} )
end
All fun and games up until now.
What I expect is to get a result in a form which resembles (mongo shell for db.categories.findOne() ):
{
"_id" : "category_name",
"value" : {
"ads" : [
{
"id" : ObjectId("4f2970e9e815f825a30014ab")
},
{
"id" : ObjectId("4f2970e9e815f825a30014b0")
},
{
"id" : ObjectId("4f2970e9e815f825a30014b6")
},
{
"id" : ObjectId("4f2970e9e815f825a30014b8")
},
{
"id" : ObjectId("4f2970e9e815f825a30014bd")
},
{
"id" : ObjectId("4f2970e9e815f825a30014c1")
},
{
"id" : ObjectId("4f2970e9e815f825a30014ca")
},
// ... and it goes on and on
]
}
}
Actually, it would be even better if I could get value to contain only array but MongoDB complains about not supporting that yet, but, with later use of finalize function, that is not a big problem I want to ask about.
Now, back to problem. What actually happens when I do map reduce is that it spits out something like :
{
"_id" : "category_name",
"value" : {
"ads" : [
{
"ads" : [
{
"ads" : [
{
"ads" : [
{
"ads" : [
{
"id" : ObjectId("4f2970d8e815f825a3000011")
},
{
"id" : ObjectId("4f2970d8e815f825a3000017")
},
{
"id" : ObjectId("4f2970d8e815f825a3000019")
},
{
"id" : ObjectId("4f2970d8e815f825a3000022")
},
// ... on and on and on
... and while I could probably work out a way to use this it just doesn't look like something I should get.
So, my questions (finally) are:
Am I doing something wrong and what is it?
I there something wrong with MongoDB map reduce (I mean besides all the usual things when compared to hadoop)?
Yes, you're doing it wrong. Inputs and outputs of map and reduce should be uniform. Because they are meant to be executed in parallel, and reduce might be run over partially reduced results. Try these functions:
var map = function() {
emit(this.category, {ads: [this._id]});
};
var reduce = function(key, values) {
var result = {ads: []};
values.forEach(function(v) {
v.ads.forEach(function(a) {
result.ads.push(a)
});
});
return result;
}
This should produce documents like:
{_id: category, value: {ads: [ObjectId("4f2970d8e815f825a3000011"),
ObjectId("4f2970d8e815f825a3000019"),
...]}}