collection update property of embedded document in array - javascript

This code uses a loop to $unset the "checked" property of all embedded documents in the "value" array, then $set the one when a condition evaluates to true.
But when the condition is true, the update block failed to update the embedded document by setting a checked: "checked", I know that because meteor:PRIMARY> db.radioOptions.find({}).pretty(); gives the same results before and after.
What am I doing wrong? and how to fix it? Thanks
meteor:PRIMARY> db.radioOptions.find({}).pretty();
{
"_id" : "jXQcsXtedQYotKQXG",
"name" : "optionType",
"value" : [
{
"name" : "1stOption",
"caption" : "1st Option"
},
{
"name" : "2ndOption",
"caption" : "2nd Option"
}
]
}
var doc = RadioOptions.findOne({name: obj.name});
if (typeof doc != 'undefined') {
doc.value.forEach(function (embdoc) {
console.log(embdoc);
RadioOptions.update(
{name: obj.name, 'value.name': obj.value},
{$unset: {'value.$.checked': ""}}
);
if (embdoc.name == obj.value) {
console.log(obj.name + " " + obj.value); //obj.value = 1stOption for example
RadioOptions.update(
{name: obj.name, 'value.name': obj.value}, //obj.name = "optionType"
{$set: {'value.$.checked': "checked"}}
);
}
})
}

Let's say that is was your objective to set the array element with the name "2ndOption" to "checked" and $unset all other array elements. You would then instead do:
var doc = RadioOptions.findOne({name: obj.name});
if (typeof doc != 'undefined') {
// You have to update every element
doc.value.forEach(function (embdoc) {
RadioOptions.update(
{ "_id": doc._id, "value.name": embdoc.name },
{ "$unset": { "value.$.checked": "" } }
)
});
// Then just set the one you want
RadioOptions.update(
{ "_id": doc._id, "value.name": "2ndOption" }, // <-- actually using a variable of course
{ "$set": { "value.$.checked": "checked" } }
)
}
Now if you had actually read all the responses on the duplicate question you were given for your original question:
How to Update Multiple Array Elements in mongodb
Then you would have seen the response there that mentioned the best way to handle all these mutliple updates was using the "Bulk" API methods in the underlying driver. And also has some useful tips on the general process of updating multiple elements.
On the "server" (not in minimongo) is the best place to to this, and all meteor collections have a .rawCollection() method which returns the collection object from the underlying node native driver.
So the general logic is:
Loop all array elements and update to "turn off"
Match the element you want to "turn on"
And best done in Bulk rather than responding back and forth for each update.

Related

Mongo script to update an object if its a certain value

I am trying to update a document in Mongo if it has a certain value within a field.
{
"_id" : ObjectId("myObject"),
"source" : "BW",
"sourceTableName" : "myTable",
"tableName" : "tier",
"type" : "main",
"fieldMappings" : [
{
"sourceField" : "tier_id", <~~~~ If this is "tier_id", I want to change it to "trmls_id"
"reportingField" : "bw_id",
"type" : "integer",
"defaultMap" : {
"shouldErrorOnConvert" : false
}
}
]
}
I tried something like
db.getCollection('entityMap').update({"sourceTableName":"myTable"}, {"fieldMappings.0.sourceField":"trmls_id":{$exists : true}}, { $set: { "fieldMappings.0.sourceField": "tier_id" } })
and I think it is failing on the $exists parameter I am setting.
Is there a more cleaner/dynamic way to do this?
The whole query I am trying to formulate is
In the table myTable
I want to check if there is a sourceField that has the value tier_id
If there is tier_id, then change it to trmls_id
Otherwise do nothing
If there is a similar StackOverflow question that answers this, I am happy to explore it! Thanks!
There is a syntax mistake and you can use positional operator $ in update part because you have already selector field in query part,
db.getCollection('entityMap').update(
{
"sourceTableName": "myTable",
"fieldMappings.sourceField": "tier_id" // if this field found then it will go to update part
},
{
$set: {
"fieldMappings.$.sourceField": "trmls_id"
}
}
)

Cannot change _id field in Mongo DB from int to objectId using foreach [duplicate]

I want update an _id field of one document. I know it's not really good practice. But for some technical reason, I need to update it.
If I try to update it I get:
db.clients.update({ _id: ObjectId("123")}, { $set: { _id: ObjectId("456")}})
Performing an update on the path '_id' would modify the immutable field '_id'
And the update is rejected. How I can update it?
You cannot update it. You'll have to save the document using a new _id, and then remove the old document.
// store the document in a variable
doc = db.clients.findOne({_id: ObjectId("4cc45467c55f4d2d2a000002")})
// set a new _id on the document
doc._id = ObjectId("4c8a331bda76c559ef000004")
// insert the document, using the new _id
db.clients.insert(doc)
// remove the document with the old _id
db.clients.remove({_id: ObjectId("4cc45467c55f4d2d2a000002")})
To do it for your whole collection you can also use a loop (based on Niels example):
db.status.find().forEach(function(doc){
doc._id=doc.UserId; db.status_new.insert(doc);
});
db.status_new.renameCollection("status", true);
In this case UserId was the new ID I wanted to use
In case, you want to rename _id in same collection (for instance, if you want to prefix some _ids):
db.someCollection.find().snapshot().forEach(function(doc) {
if (doc._id.indexOf("2019:") != 0) {
print("Processing: " + doc._id);
var oldDocId = doc._id;
doc._id = "2019:" + doc._id;
db.someCollection.insert(doc);
db.someCollection.remove({_id: oldDocId});
}
});
if (doc._id.indexOf("2019:") != 0) {... needed to prevent infinite loop, since forEach picks the inserted docs, even throught .snapshot() method used.
Here I have a solution that avoid multiple requests, for loops and old document removal.
You can easily create a new idea manually using something like:_id:ObjectId()
But knowing Mongo will automatically assign an _id if missing, you can use aggregate to create a $project containing all the fields of your document, but omit the field _id. You can then save it with $out
So if your document is:
{
"_id":ObjectId("5b5ed345cfbce6787588e480"),
"title": "foo",
"description": "bar"
}
Then your query will be:
db.getCollection('myCollection').aggregate([
{$match:
{_id: ObjectId("5b5ed345cfbce6787588e480")}
}
{$project:
{
title: '$title',
description: '$description'
}
},
{$out: 'myCollection'}
])
You can also create a new document from MongoDB compass or using command and set the specific _id value that you want.
As a very small improvement to the above answers i would suggest using
let doc1 = {... doc};
then
db.dyn_user_metricFormulaDefinitions.deleteOne({_id: doc._id});
This way we don't need to create extra variable to hold old _id.
Slightly modified example of #Florent Arlandis above where we insert _id from a different field in a document:
> db.coll.insertOne({ "_id": 1, "item": { "product": { "id": 11 } }, "source": "Good Store" })
{ "acknowledged" : true, "insertedId" : 1 }
> db.coll.aggregate( [ { $set: { _id : "$item.product.id" }}, { $out: "coll" } ]) // inserting _id you want for the current collection
> db.coll.find() // check that _id is changed
{ "_id" : 11, "item" : { "product" : { "id" : 11 } }, "source" : "Good Store" }
Do not use $match filter + $out as in #Florent Arlandis's answer since $out fully remove data in collection before inserting aggregate result, so effectively you will loose all data that don't match to $match filter

What is the addressing issue with my complex document update() in MongoDB?

I have been unable to reach into my MongoDB collection and change a value in a complex document. I have tried more variations than the one example shown below, all sorts of variations, but they fail.
I want to change the Value of the Key "air" from "rain" to "clear". In real life, I will not know that the current Value of the Key "air" is "rain".
Note, I am not using the MongoDB _id Object and would like to accomplish this without using it.
3 documents in the weatherSys collection:
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"SanFrancisco" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
{
"_id" : ObjectId("58a638fb1831c61917f921c6"),
"LosAngeles" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
{
"_id" : ObjectId("58a638fb1831c61917f921c7"),
"SanDiego" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
var docKey = "LosAngeles";
var subKey = "air";
var newValue = "clear";
var query = {};
//var queryKey = docKey + ".$";
query[query] = subKey; // query = { }
var set = {};
var setKey = docKey + ".0." + subKey;
set[setKey] = newValue; // set = { "weather.0.air" : "clear" }
db.collection('weatherSys').update(query, { $set: set }, function(err, result) {
if (err) throw err;
});
UPDATE-1:
Ok, so I was hoping I could find a layout a bit simpler than you had suggested but I failed. Everything I tried was not addressable at the "air" Key level. So I copy and pasted your exact JSON into my collection and ran it. I'm using MongoChef to manipulate and test the collection.
Here is my new layout drived from pasting your JSON in 3 times to create 3 documents:
When I then attempted to update the "San Francisco" document's "air" key I got an unexpected result. Rather than updating "air":"dry" it created a new "air" key in the "San Francisco" Object:
So I thought ok, lets try the update again and see what happens:
As you can see it updated the "air" key that it had previously created. I could fight this out and try to make it work "my" way but I just want it to work so I reconfigure my collection layout again, along the lines of what is "working":
And run the update again:
Then I verify it by running the update again:
It works, I am updating properly in a multi-document environment. So this is my current working collection layout:
I have a couple of questions about this-
I am using the top level Key "weather" in every document. It adds nothing to the information within the document. Is there a layout design change that would not necessitate that Key and the overhead it brings along?
Lets say I have to use the "weather" key. Its value is an array, but that array only has one element, the Object which contains the Keys: city, sky, air, and ground. Does addressing necessitate the use of an array with only one element? Or could I get rid of it. Instead of "weather":[{}] could the design be "weather":{} or would I get into non addressability issues again?
It appears I can now update() any of the Values for the Keys: air, sky, and ground, but what is the find() structure to say READ the Value of the Key "ground" in one of the documents?
----> OK, I think I've got this question #3-
db.weatherSys.find({ "weather.city" : "San Francisco" }, { "weather.ground": 1 })
In the original collection layout that you had suggested, could you explain to me why it did not update as you and I had expected but instead created a new the "city" object?
A lot here. I appreciate your sticking with it.
You can't use positional operator for querying the array by its key.
You can access the weather array by index, but that means you know the array index.
For example if you want to update air element value in weather array.
db.collection('weatherSys').update( {}, { $set: { "weather.1.air" : "clear"} } );
Update:
Unfortunately, I can't see any way to update the values without knowing the array index for key.
You don't need query object as your keys are unique .
db.collection('weatherSys').update( {}, { $set: { "SanFrancisco.1.air" : "clear"} } );
or
Other variant if you want to make sure the key exists.
db.collection('weatherSys').update( { "SanFrancisco": { $exists: true } }, { $set: { "SanFrancisco.1.air" : "clear"} } );
Not sure if you can but if you can update your structure to below.
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"weather" : [
{
"city": "LosAngeles",
"sky" : "grey" ,
"air" : "rain" ,
"ground" : "wet"
}
]
}
You can now use $positional operator for update.
db.collection('weatherSys').update( {"weather.city":"LosAngeles"}, { $set: { "weather.$.air" : "clear"} } );
I am using the top level Key "weather" in every document. It adds
nothing to the information within the document. Is there a layout
design change that would not necessitate that Key and the overhead it
brings along?
The only layout that I can think of is promoting all the embedded properties to the top level. Sorry, not sure why I didn't think of this the first time around. Sometimes you just need a right question to get the right answer.
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"city": "LosAngeles",
"sky" : "grey",
"air" : "rain",
"ground" : "wet"
}
All the updates will be simply top level updates.
db.collection('weatherSys').update( {"city":"LosAngeles"}, { $set: { "air" : "clear"} } );
Lets say I have to use the "weather" key. Its value is an array, but
that array only has one element, the Object which contains the Keys:
city, sky, air, and ground. Does addressing necessitate the use of an
array with only one element? Or could I get rid of it. Instead of
"weather":[{}] could the design be "weather":{} or would I get into
non addressability issues again?
N/A if you are okay with first suggestion.
It appears I can now update() any of the Values for the Keys: air,
sky, and ground, but what is the find() structure to say READ the
Value of the Key "ground" in one of the documents?
db.weatherSys.find({ "city" : "San Francisco" }, { "ground": 1 })
In the original collection layout that you had suggested, could you
explain to me why it did not update as you and I had expected but
instead created a new the "city" object?
That is a copy paste error. I meant to suggest the working layout you have right now. Updated my previous layout.

Meteor: Return only single object in nested array within collection

I'm attempting to filter returned data sets with Meteor's find().fetch() to contain just a single object, it doesn't appear very useful if I query for a single subdocument but instead I receive several, some not even containing any of the matched terms.
I have a simple mixed data collection that looks like this:
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e7"),
"name" : "Entertainment",
"items" : [
{
"_id" : ObjectId("57a38b5f2bd9ac8225caff06"),
"slug" : "this-is-a-long-slug",
"title" : "This is a title"
},
{
"_id" : ObjectId("57a38b835ac9e2efc0fa09c6"),
"slug" : "mc",
"title" : "Technology"
}
]
}
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e8"),
"name" : "Sitewide",
"items" : [
{
"_id" : ObjectId("57a38bc75ac9e2efc0fa09c9"),
"slug" : "example",
"name" : "Single Example"
}
]
}
I can easily query for a specific object in the nested items array with the MongoDB shell as this:
db.categories.find( { "items.slug": "mc" }, { "items.$": 1 } );
This returns good data, it contains just the single object I want to work with:
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e7"),
"items" : [
{
"_id" : ObjectId("57a38b985ac9e2efc0fa09c8")
"slug" : "mc",
"name" : "Single Example"
}
]
}
However, if a similar query within Meteor is directly attempted:
/* server/publications.js */
Meteor.publish('categories.all', function () {
return Categories.find({}, { sort: { position: 1 } });
});
/* imports/ui/page.js */
Template.page.onCreated(function () {
this.subscribe('categories.all');
});
Template.page.helpers({
items: function () {
var item = Categories.find(
{ "items.slug": "mc" },
{ "items.$": 1 } )
.fetch();
console.log('item: %o', item);
}
});
The outcome isn't ideal as it returns the entire matched block, as well as every object in the nested items array:
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e7"),
"name" : "Entertainment",
"boards" : [
{
"_id" : ObjectId("57a38b5f2bd9ac8225caff06")
"slug" : "this-is-a-long-slug",
"name" : "This is a title"
},
{
"_id" : ObjectId("57a38b835ac9e2efc0fa09c6")
"slug" : "mc",
"name" : "Technology"
}
]
}
I can then of course filter the returned cursor even further with a for loop to get just the needed object, but this seems unscalable and terribly inefficient while dealing with larger data sets.
I can't grasp why Meteor's find returns a completely different set of data than MongoDB's shell find, the only reasonable explanation is both function signatures are different.
Should I break up my nested collections into smaller collections and take a more relational database approach (i.e. store references to ObjectIDs) and query data from collection-to-collection, or is there a more powerful means available to efficiently filter large data sets into single objects that contain just the matched objects as demonstrated above?
The client side implementation of Mongo used by Meteor is called minimongo. It currently only implements a subset of available Mongo functionality. Minimongo does not currently support $ based projections. From the Field Specifiers section of the Meteor API:
Field operators such as $ and $elemMatch are not available on the client side yet.
This is one of the reasons why you're getting different results between the client and the Mongo shell. The closest you can get with your original query is the result you'll get by changing "items.$" to "items":
Categories.find(
{ "items.slug": "mc" },
{ "items": 1 }
).fetch();
This query still isn't quite right though. Minimongo expects your second find parameter to be one of the allowed option parameters outlined in the docs. To filter fields for example, you have to do something like:
Categories.find(
{ "items.slug": "mc" },
{
fields: {
"items": 1
}
}
).fetch();
On the client side (with Minimongo) you'll then need to filter the result further yourself.
There is another way of doing this though. If you run your Mongo query on the server, you won't be using Minimongo, which means projections are supported. As a quick example, try the following:
/server/main.js
const filteredCategories = Categories.find(
{ "items.slug": "mc" },
{
fields: {
"items.$": 1
}
}
).fetch();
console.log(filteredCategories);
The projection will work, and the logged results will match the results you see when using the Mongo console directly. Instead of running your Categories.find on the client side, you could instead create a Meteor Method that calls your Categories.find on the server, and returns the results back to the client.

how to push a dictionary to a nested array with mongodb?

i have data that looks like this in my database
> db.whocs_up.find()
{ "_id" : ObjectId("52ce212cb17120063b9e3869"), "project" : "asnclkdacd", "users" : [ ] }
and i tried to add to the 'users' array like thus:
> db.whocs_up.update({'users.user': 'usex', 'project' : 'asnclkdacd' },{ '$addToSet': { 'users': {'user':'userx', 'lastactivity' :2387843543}}},true)
but i get the following error:
Cannot apply $addToSet modifier to non-array
same thing happens with push operator, what im i doing wrong?
im on 2.4.8
i tried to follow this example from here:
MongoDB - Update objects in a document's array (nested updating)
db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} ,
{$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
false ,
true)
the python tag is because i was working with python when i ran into this, but it does nto work on the mongo shell as you can see
EDIT ============================== GOT IT TO WORK
apparently if i modify the update from
db.whocs_up.update({'users.user': 'usex', 'project' : 'asnclkdacd' },{ '$addToSet': { 'users': {'user':'userx', 'lastactivity' :2387843543}}},true)
to this:
db.whocs_up.update({'project' : 'asnclkdacd' },{ '$addToSet': { 'users': {'user':'userx', 'lastactivity' :2387843543}}},true)
it works, but can anyone explain why the two do not achieve the same thing, in my understanding they should have referenced the same document and hence done the same thing,
What does the addition of 'users.user': 'userx' change in the update? does it refer to some inner document in the array rather than the document as a whole?
This is a known bug in MongoDB (SERVER-3946). Currently, an update with $push/$addToSet with a query on the same field does not work as expected.
In the general case, there are a couple of workarounds:
Restructure your update operation to not have to query on a field that is also to be updated using $push/$addToSet (as you have done above).
Use the $all operator in the query, supplying a single-value array containing the lookup value. e.g. instead of this:
db.foo.update({ x : "a" }, { $addToSet : { x : "b" } }, true)
do this:
db.foo.update({ x : { $all : ["a"] } }, { $addToSet : { x : "b" } } , true)
In your specific case, I think you need to re-evaluate the operation you're trying to do. The update operation you have above will add a new array entry for each unique (user, lastactivity) pair, which is probably not what you want. I assume you want a unique entry for each user.
Consider changing your schema so that you have one document per user:
{
_id : "userx",
project : "myproj",
lastactivity : 123,
...
}
The update operation then becomes something like:
db.users.update({ _id : "userx" }, { $set : { lastactivity : 456 } })
All users in a given project may still be looked up efficiently by adding a secondary index on project.
This schema also avoids the unbounded document growth of the above schema, which is better for performance.

Categories