Mongo DB Update to a sub array document - mongodb

I have a structure
{
"_id" : ObjectId("562dfb4c595028c9r74fda67"),
"office_id" : "123456",
"employee" : [
{
"status" : "declined",
"personId" : "123456",
"updated" : NumberLong("1428407042401")
}
]
}
This office can have multiple persons.Is there a way if I want to update the employee status for all the person under that specific office_id to say "approved".I am trying the same through plain mongo java driver.What I am trying is get all the office id using a query builder , then iterate over the list and save the document.Somewhat I am not satisfied with the iterative approach(fetch,iterate and save ) that I am following.Please suggest if there is alternative way.

You can update using the $ positional operator:
db.collection.update(
{
"office_id" : "123456",
"employee.status": "declined"
},
{
"$set": { "employee.$.status": "approved" }
}
);
The positional operator saves the index (0 in the case above) of the element from the array that matched the query. This means that if you knew the position of the element beforehand (which is nearly impossible in a real life case), you could just change the update statement to: {"$set": {"employee.0.status": "approved"}}.
Please note that the $ positional operator (for now) updates the first relevant document ONLY, there is a JIRA ticket for this.
EDIT:
Using the Java driver, the above update may be done like so (untested):
BasicDBObject update = new BasicDBObject();
BasicDBObject query = new BasicDBObject();
query.put("office_id", "123456");
query.put("employee.status", "declined");
BasicDBObject set = new BasicDBObject("$set", update);
update.put(""employee.$.status", "approved");
collection.update(query, set);

Related

How do I update values in a nested array?

I would like to preface this with saying that english is not my mother tongue, if any of my explanations are vague or don't make sense, please let me know and I will attempt to make them clearer.
I have a document containing some nested data. Currently product and customer are arrays, I would prefer to have them as straight up ObjectIDs.
{
"_id" : ObjectId("5bab713622c97440f287f2bf"),
"created_at" : ISODate("2018-09-26T13:44:54.431Z"),
"prod_line" : ObjectId("5b878e4c22c9745f1090de66"),
"entries" : [
{
"order_number" : "123",
"product" : [
ObjectId("5ba8a0e822c974290b2ea18d")
],
"customer" : [
ObjectId("5b86a20922c9745f1a6408d4")
],
"quantity" : "14"
},
{
"order_number" : "456",
"product" : [
ObjectId("5b878ed322c9745f1090de6c")
],
"customer" : [
ObjectId("5b86a20922c9745f1a6408d5")
],
"quantity" : "12"
}
]
}
I tried using the following query to update it, however that proved unsuccessful as Mongo didn't behave quite as I had expected.
db.Document.find().forEach(function(doc){
doc.entries.forEach(function(entry){
var entry_id = entry.product[0]
db.Document.update({_id: doc._id}, {$set:{'product': entry_id}});
print(entry_id)
})
})
With this query it sets product in the root of the object, not quite what I had hoped for. What I was hoping to do was to iterate through entries and change each individual product and customer to be only their ObjectId and not an array. Is it possible to do this via the mongo shell or do I have to look for another way to accomplish this? Thanks!
In order to accomplish your specified behavior, you just need to modify your query structure a bit. Take a look here for the specific MongoDB documentation on how to accomplish this. I will also propose an update to your code below:
db.Document.find().forEach(function(doc) {
doc.entries.forEach(function(entry, index) {
var productElementKey = 'entries.' + index + '.product';
var productSetObject = {};
productSetObject[productElementKey] = entry.product[0];
db.Document.update({_id: doc._id}, {$set: productSetObject});
print(entry_id)
})
})
The problem that you were having is that you were not updating the specific element within the entries array, but rather adding a new key to the top-level of the document named product. Generally, in order to set the value of an inner document within an array, you need to specify the array key first (entries in this case) and the inner document key second (product in this case). Since you are trying to set specific elements within the entries array, you need to also specify the index in your query object, I have specified above.
In order to update the customer key in the inner documents, simply switch out the product for customer in my above code.
You're trying to add a property 'product' directly into your document with this line
db.Document.update({_id: doc._id}, {$set:{'product': entry_id}});
Try to modify all your entries first, then update your document with this new array of entries.
db.Document.find().forEach(function(doc){
let updatedEntries = [];
doc.entries.forEach(function(entry){
let newEntry = {};
newEntry["order_number"] = entry.order_number;
newEntry["product"] = entry.product[0];
newEntry["customer"] = entry.customer[0];
newEntry["quantity"] = entry.quantity;
updatedEntries.push(newEntry);
})
db.Document.update({_id: doc._id}, {$set:{'entries': updatedEntries}});
})
You'll need to enumerate all the documents and then update the documents one and a time with the value store in the first item of the array for product and customer from each entry:
db.documents.find().snapshot().forEach(function (elem) {
elem.entries.forEach(function(entry){
db.documents.update({
_id: elem._id,
"entries.order_number": entry.order_number
}, {
$set: {
"entries.$.product" : entry.product[0],
"entries.$.customer" : entry.customer[0]
}
}
);
});
});
Instead of doing 2 updates each time you could possibly use the filtered positional operator to do all updates to all arrays items within one update query.

MongoDB get object id by finding on another column value

I am new to querying dbs and especially mongodb.If I run :
db.<customers>.find({"contact_name: Anny Hatte"})
I get:
{
"_id" : ObjectId("55f7076079cebe83d0b3cffd"),
"company_name" : "Gap",
"contact_name" : "Anny Hatte",
"email" : "ahatte#gmail.com"
}
I wish to get the value of the "_id" attribute from this query result. How do I achieve that?
Similarly, if I have another collection, named items, with the following data:
{
"_id" : ObjectId("55f7076079cebe83d0b3d009"),
"_customer" : ObjectId("55f7076079cebe83d0b3cfda"),
"school" : "St. Patrick's"
}
Here, the "_customer" field is the "_id" of the customer collection (the previous collection). I wish to get the "_id", the "_customer" and the "school" field values for the record where "_customer" of items-collection equals "_id" of customers-collection.
How do I go about this?
I wish to get the value of the "_id" attribute from this query result.
How do I achieve that?
The find() method returns a cursor to the results, which you can iterate and retrieve the documents in the result set. You can do this using forEach().
var cursor = db.customers.find({"contact_name: Anny Hatte"});
cursor.forEach(function(customer){
//access all the attributes of the document here
var id = customer._id;
})
You could make use of the aggregation pipeline's $lookup stage that has been introduced as part of 3.2, to look up and fetch the matching rows in some other related collection.
db.customers.aggregate([
{$match:{"contact_name":"Anny Hatte"}},
{$lookup:{
"from":"items",
"localField":"_id",
"foreignField":"_customer",
"as":"items"
}}
])
In case you are using a previous version of mongodb where the stage is not supported, then, you would need to fire an extra query to lookup the items collection, for each customer.
db.customers.find(
{"contact_name":"Anny Hatte"}).map(function(customer){
customer["items"] = [];
db.items.find({"_customer":customer._id}).forEach(function(item){
customer.items.push(item);
})
return customer;
})

Override existing Docs in production MongoDB

I have recently changed one of my fields from object to array of objects.
In my production I have only 14 documents with this field, so I decided to change those fields.
Is there any best practices to do that?
As it is in my production I need to do it in a best way possible?
I got the document Id's of those collections.like ['xxx','yyy','zzz',...........]
my doc structure is like
_id:"xxx",option1:{"op1":"value1","op2":"value2"},option2:"some value"
and I want to change it like(converting object to array of objects)
_id:"xxx",option1:[{"op1":"value1","op2":"value2"},
{"op1":"value1","op2":"value2"}
],option2:"some value"
Can I use upsert? If so How to do it?
Since you need to create the new value of the field based on the old value, you should retrieve each document with a query like
db.collection.find({ "_id" : { "in" : [<array of _id's>] } })
then iterate over the results and $set the value of the field to its new value:
db.collection.find({ "_id" : { "in" : [<array of _id's>] } }).forEach(function(doc) {
oldVal = doc.option1
newVal = compute_newVal_from_oldVal(oldVal)
db.collection.update({ "_id" : doc._id }, { "$set" : { "option" : newVal } })
})
The document structure is rather schematic, so I omitted putting in actual code to create newVal from oldVal.
Since it is an embedded document type you could use push query
db.collectionname.update({_id:"xxx"},{$push:{option1:{"op1":"value1","op2":"value2"}}})
This will create document inside embedded document.Hope it helps

MongoDB: $in with an ObjectId array

Just a quick question about something I've just experienced and I'm still thinking about why:
mongos> db.tickets.count({ "idReferenceList" : { "$in" : [ { "$oid" : "53f1f09f2cdcc8f339e5efa2"} , { "$oid" : "5409ae2e2cdc31c5aa0ce0a5"}]}});
0
mongos> db.tickets.count({ "idReferenceList" : { "$in" : [ ObjectId("53f1f09f2cdcc8f339e5efa2") , ObjectId("5409ae2e2cdc31c5aa0ce0a5")]}});
2
I thought that both $oid and ObjectId spelling formats where exactly the same for MongoDB. Does anyone know why with the first query return 0 results and with the second one is returning 2 (the right answer)?
Furthermore, I'm using Morphia framework which uses MongoDB Java driver to interact with MongoDB. I've realised that there exists a problem by searching with $in operator in ObjectIds arrays over fields that are not _id by executing this lines of code:
List< ObjectId > fParams = new ArrayList< ObjectId >();
fParams.add(...);
Query<Ticket> query = genericDAO.createQuery();
query.field("idReferenceList").in(fParams);
result = genericDAO.find(query).asList();
Thank you very much in advance.
Regards,
Luis Cappa
Both these formats are valid representations of an object id in MongoDB, according to the documentation,
http://docs.mongodb.org/manual/reference/mongodb-extended-json/
and they represented differently in the two modes,
Strict Mode mongo Shell Mode
----------- ----------------
{ "$oid": "<id>" } ObjectId( "<id>" )
So, to query fields which contain objectid, from the shell/console mode, you need to use ObjectId("<id>").
Which is the syntax to be followed in the mongo shell mode.
Hence the query:
db.tickets.count({ "idReferenceList" : { "$in" : [ ObjectId("53f1f09f2cdcc8f339e5efa2") , ObjectId("5409ae2e2cdc31c5aa0ce0a5")]}});
would return you row count.
Now to do it via the Java API,
You need to do it as below:
String[] ids = {"53f1f09f2cdcc8f339e5efa2","5409ae2e2cdc31c5aa0ce0a5"};
ObjectId[] objarray = new ObjectId[ids.length];
for(int i=0;i<ids.length;i++)
{
objarray[i] = new ObjectId(ids[i]);
}
BasicDBObject inQuery = new BasicDBObject("$in", objarray);
BasicDBObject query = new BasicDBObject("idReferenceList", inQuery);
DBCursor cursor = db.collection.find(query);
while(cursor.hasNext())
{
DBObject doc = cursor.next();
// process the doc.
}
I faced the same issue.
I resolved like this way.
db.collection('post').find({ 'postIds': { $elemMatch: { $in:
deletedPosts.map(_post => {ObjectId(_post._id)}) } } })

MongoDB:Spring data:- Embedded Array Count

How to identify the number of elements in the particular embedded document (or) how to find the number of elements in the embedded array?
Award
{
"brand" : [
{
"name" : "multi",
"descr" : "Multpple"
},
{
"name" : "multi",
"descr" : "two"
},
{
"name" : "multi",
"descr" : "three"
}
],
"name" : "Test",
"narname" : "Nar"
}
For Eg: In the above document how to find the number of elements that is inside the embedded array BRAND using Spring Data.?
Any pointers would be greatly appreciated !
I don't think there is a method which can get the answer directly.
You can use aggregate to implement it. For example, if you want to get the count of elements in array brand in a specific document, this way should be available (run on mongo shell):
db.Award.aggregate({$match:{_id:id}}, {$unwind:"$brand"}, {$group:{_id:"$_id", count:{$sum:1}}});
count is the result you want.
Then implement that using spring-data-mongodb syntax.
-------------- APPENDED ---------------------
// You can find the relative aggregation method from MongoTemplate.java file to handle your requirements.
// For exmaple:
// public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType)
// The version is around spring-data-mongodb-1.5.0 or higher.
// Below I call the mongo-java-driver directly because I find it needs some time to learn it from spring-data-mongodb. :)
protected int getArraySize(Object id, String collName) {
// Attention: make sure id is in the correct data type because the following statement would not convert it automatically.
// Issue codes according to this command line:
// db.Award.aggregate({$match:{_id:id}}, {$unwind:"$brand"}, {$group:{_id:"$_id", count:{$sum:1}}});
DBObject match = BasicDBObjectBuilder.start().push("$match").append("_id", id).get();
DBObject unwind = new BasicDBObject("$unwind", "$brand");
DBObject group = BasicDBObjectBuilder.start().push("$group").append("_id", "$_id").push("count").append("$sum", 1).get();
List<DBObject> pipeline = Arrays.asList(match, unwind, group);
// This aggregate method is supported in higher version of mongo-java-driver, here I use is 2.12.3
AggregationOutput aggr = this.mongoTemplate.getCollection(collName).aggregate(pipeline);
for (DBObject dbo : aggr.results()) {
Object count = dbo.get("count");
if (count instanceof Number) {
return ((Number)count).intValue();
}
}
return 0;
}