Update a nested document filed and increment in mongodb - java

I want to update a nested document filed if present increment an old value with new value or insert a new document.
Data
New Zealand,Waikato,Hamilton,1004
New Zealand,Waikato,Auckland,145
New Zealand,Otago,Dunedin,1068
Json
{ "_id" : ObjectId("55e7d2a72f68907c17cfcb2f"), "country" : "New Zealand",
"regions" : [ { "region" : "Waikato", "size" : 1004 },
{ "region" : "Waikato", "size" : 145 }, { "region" : "Otago", "size" : 1068 } ] }
In document regions array is dynamic in nature. In above document I need to update an increment field size value of ~Waikato`. Instead of putting an another record in array of regions.
My code
BasicDBObject query = new BasicDBObject();
query.put("country", "New Zealand");
query.put("regions.$.region", "Waikato");
BasicDBObject data = new BasicDBObject().append("$inc", new BasicDBObject().append("regions.$.size", 145));
BasicDBObject command = new BasicDBObject();
command.put("$set", data);
collection.update(query, command, true, false);
I need output like these:
{ "_id" : ObjectId("55e7d2a72f68907c17cfcb2f"), "country" : "New Zealand", "regions" : [ { "region" : "Waikato", "size" : 1149 }, { "region" : "Otago", "size" : 1068 } ] }
Please suggest me on these issue.

Your positional $ operator only belongs in the "update portion and not the query. Also you need to .append() in the query otherwise you overwrite:
BasicDBObject query = new BasicDBObject();
query.put("country", "New Zealand");
query.append("regions.region", "Waikato");
BasicDBObject update = new BasicDBObject()
.append("$inc", new BasicDBObject().append("regions.$.size", 145));
collection.update(query, update, true, false);
Basically looks like this ( shell wise ) :
collection.update(
{ "country": "New Zealand", "regions.region": " "Waikato" },
{ "$inc": regions.$.size": 145 },
true,
false
)

Related

How to project more than the first sub-document when using $elemMatch

I have a collection with documents of the following form:
{
"_id" : { "$oid" : "67bg............"},
"ID" : "xxxxxxxx",
"senses" : [
{
"word" : "hello",
"lang" : "EN",
"source" : "EN_DICTIONARY"
},
{
"word" : "coche",
"lang" : "ES",
"source" : "ES_DICTIONARY"
},
{
"word" : "bye",
"lang" : "EN",
"source" : "EN_DICTIONARY"
}
]
}
I want to find all documents that match at least one sense with lang=X and source=Y and return the matched Documents with only those senses which match lang=X and source=Y.
I tried this:
DBObject sensesQuery = new BasicDBObject();
sensesQuery.put("lang", "EN");
sensesQuery.put("source", "EN_DICTIONARY");
DBObject matchQuery = new BasicDBObject("$elemMatch",sensesQuery);
DBObject fields = new BasicDBOject();
fields.put("senses",matchQuery);
DBObject projection = new BasicDBObject();
projection.put("ID",1)
projection.put("senses",matchQuery);
DBCursor cursor = collection.find(fields,projection)
while(cursor.hasNext()) {
...
}
My query works for matching documents, but not for the projection. Taking the above document as an example, if I run my query I get this result:
{
"_id" : { "$oid" : "67bg............"},
"ID" : "xxxxxxxx",
"senses" : [
{
"word" : "hello",
"lang" : "EN",
"source" : "EN_DICTIONARY"
}
]
}
But I want this :
{
"_id" : { "$oid" : "67bg............"},
"ID" : "xxxxxxxx",
"senses" : [
{
"word" : "hello",
"lang" : "EN",
"source" : "EN_DICTIONARY"
},
{
"word" : "bye",
"lang" : "EN",
"source" : "EN_DICTIONARY"
}
]
}
I read about aggregation but I did not understand how to use it in the MongoDB Java driver.
Thanks
You are using the $elemMatch operator on the projection aswell as on the filter.
From the docs
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
So, the behaviour you are seeing is the expected behaviour for elemMatch-in-a-projection.
If you want to project all sub documents in the senses array within documents which match the filter condition then you could use this:
projection.put("senses", 1);
But, if you want to project only those sub documents which match your filter condition then $elemMatch will not work for you since it only ever returns the first element matching the $elemMatch condition. Your alternative is to use the aggregation framework, for example:
db.collection.aggregate([
// matches documents with a senses sub document having the given lang and source values
{$match: {'senses.lang': 'EN', 'senses.source': 'EN_DICTIONARY'}},
// projects on the senses sub document and filters the output to only return sub
// documents having the given lang and source values
{$project: {
senses: {
$filter: {
input: "$senses",
as: "sense",
cond: { $eq: [ "$$sense.lang", 'EN' ], $eq: [ "$$sense.source", 'EN_DICTIONARY' ] }
}
}
}
}
])
Here's that aggregation call using the MongoDB Java driver:
Document filter = new Document("senses.lang", "EN").append("senses.source", "EN_DICTIONARY");
DBObject filterExpression = new BasicDBObject();
filterExpression.put("input", "$senses");
filterExpression.put("as", "sense");
filterExpression.put("cond", new BasicDBObject("$and", Arrays.<Object>asList(
new BasicDBObject("$eq", Arrays.<Object>asList("$$sense.lang", "EN")),
new BasicDBObject("$eq", Arrays.<Object>asList("$$sense.source", "EN_DICTIONARY")))
));
BasicDBObject projectionFilter = new BasicDBObject("$filter", filterExpression);
AggregateIterable<Document> documents = collection.aggregate(Arrays.asList(
new Document("$match", filter),
new Document("$project", new Document("senses", projectionFilter))));
for (Document document : documents) {
logger.info("{}", document.toJson());
}
The resulting output is:
2017-10-01 17:15:39 [main] INFO c.s.mongo.MongoClientTest - { "_id" : { "$oid" : "59d10cdfc26584cd8b7a0d3b" }, "senses" : [{ "word" : "hello", "lang" : "EN", "source" : "EN_DICTIONARY" }, { "word" : "bye", "lang" : "EN", "source" : "EN_DICTIONARY" }] }
Update 1: following this comment:
After a long period of testing, trying to understand why the query was slow, I noticed that the "$match" parameter does not work, the query should select only records that have at least one sense with source = Y AND lang = X and project them , but the query also returns me documents with senses = []
This filter: new Document("senses.lang", "EN").append("senses.source", "EN_DICTIONARY") will not match documents which have no senses attribute nor will it match documents which have an empty senses attribute. To verify this I added the following documents to my own collection:
{
"_id" : ObjectId("59d72a24c26584cd8b7b70a5"),
"ID" : "yyyyyyyy"
}
{
"_id" : ObjectId("59d72a3ac26584cd8b7b70ae"),
"ID" : "zzzzzzzzz",
"senses" : []
}
And re ran the above code and I still get the desired result.
I suspect your statment that the above code does not work is either a false negative or the documents you are querying are different to the sample I have been working with.
To help you diagnose this issue for yourself you could ...
Play around with other operators e.g. the $match stage behaves the same with and without an $exists operator:
new Document("senses", new BasicDBObject("$exists", true))
.append("senses.lang", new BasicDBObject("$eq", "EN"))
.append("senses.source", new BasicDBObject("$eq", "EN_DICTIONARY"))
Remove the $project stage to see exactly what the $match stage produces.

Mongodb - update specific array element

I have a collection "prefs" with document structure as below
{
_id: {
userId: "abc"
},
val: {
status: 1,
prefs: [
{
value: "condition",
lastSent: ISODate("2017-07-17T23:46:53.717Z")
}
],
deal: 2,
prevDeal: 3
}
}
I am trying to update the date field lastSent with a condition on userId and status. Below are the queries that I derieved from my Java code.
Select Query:
{ "_id" : { "userId" : "abc"} , "val.status" : 1 , "val.prefs.value" : "condition"}
Update Query:
{ "$set" : { "val.prefs.$.lastSent" : { "$date" : "2017-07-17T23:50:07.009Z"}}}
The above query is giving error as follows:
The dotted field 'prefs.$.lastSent' in 'val.prefs.$.lastSent' is not valid for storage.
How do I achieve this?
Below is my Java code:
BasicDBObject _idObject = new BasicDBObject();
_idObject.put("userId", "abc");
BasicDBObject _selectQuery = new BasicDBObject();
_selectQuery.put("_id", _idObject);
_selectQuery.put("val.status", 1);
_selectQuery.put("val.prefs.value", "condition");
BasicDBObject _valueUpdateQuery = new BasicDBObject();
_valueUpdateQuery.put("prefs.$.lastSent", lastSent);
BasicDBObject _updateQuery = new BasicDBObject();
_updateQuery.put("$set", new BasicDBObject("val", _valueUpdateQuery));
prefs.update(_selectQuery, _updateQuery, true, true);
I just tested with your code in mongo shell this codes works fine you don't have to mention
$date
and i used this code for updating date
db.getCollection('tester').update({ "_id" : { "userId" : "abc"} , "val.status" : 1 , "val.prefs.value" : "condition"},{ "$set" : { "val.prefs.$.lastSent" : new Date()}})

Multiple update in mongodb using java

I've this document:
{
"_id" : ObjectId("54140782b6d2ca6018585093"),
"user_id" : ObjectId("53f4ae1ae750619418a20467"),
"date" : ISODate("2014-09-13T08:59:46.709Z"),
"type" : 0,
"tot" : 2,
"additional_info" : {
"item_id" : ObjectId("540986159ef9ebafd3dcb5d0"),
"shop_id" : ObjectId("53f4cc5a6e09f788a103d0a4"),
"ap_id" : ObjectId("53f4cc5a6e09f788a103d0a5")
},
"transactions" : [
{
"_id" : ObjectId("54140782b6d2ca6018585091"),
"date_creation" : ISODate("2014-09-13T08:59:46.711Z"),
"type" : -1
},
{
"_id" : ObjectId("54140782b6d2ca6018585092"),
"date_creation" : ISODate("2014-09-13T08:59:46.788Z"),
"type" : 1
}
]
}
and I need to add 2 more field to the first transaction opbject:
- date_execution: date
- result: this bson document
{ "server_used" : "xxx.xxx.xxx.xxx:27017" , "ok" : 1 , "n" : 1 , "updated_executed" : true} (m_OR.getDocument() in the following code example)
to obtaing that document
{
"_id" : ObjectId("54140811b6d25137753c1a1a"),
"user_id" : ObjectId("53f4ae1ae750619418a20467"),
"date" : ISODate("2014-09-13T09:02:09.098Z"),
"type" : 0,
"tot" : 2,
"additional_info" : {
"item_id" : ObjectId("540986159ef9ebafd3dcb5d0"),
"shop_id" : ObjectId("53f4cc5a6e09f788a103d0a4"),
"ap_id" : ObjectId("53f4cc5a6e09f788a103d0a5")
},
"transactions" : [
{
"_id" : ObjectId("54140811b6d25137753c1a18"),
"date_creation" : ISODate("2014-09-13T09:02:09.100Z"),
"type" : -1,
"result" : {
"server_used" : "xxx.xxx.xxx.xxx:27017",
"ok" : 1,
"n" : 1,
"updated_executed" : true
},
"date_execution" : ISODate("2014-09-13T09:02:15.370Z")
},
{
"_id" : ObjectId("54140811b6d25137753c1a19"),
"date_creation" : ISODate("2014-09-13T09:02:09.179Z"),
"type" : 1
}
]
}
The only way I was able to do that is the do 2 separates updates (update is a my wrapper funciont that execute the real updates in mongodb and it works fine):
// where
BasicDBObject query = new BasicDBObject();
query.append("transactions._id", m_Task.ID());
// new value for result - 1st upd
BasicDBObject value = new BasicDBObject();
value.put("$set",new BasicDBObject("transactions.$.date_execution",new Date()));
update(this._systemDB, "activities", query, value);
// new value for date_execution - 2nd upd
value = new BasicDBObject();
value.put("$set",new BasicDBObject("transactions.$.result",m_OR.getDocument()));
update(this._systemDB, "activities", query, value);
If I try to do this:
BasicDBObject value = new BasicDBObject();
value.put("$set",new BasicDBObject("transactions.$.date_execution",new Date()));
value.put("$set",new BasicDBObject("transactions.$.result",m_OR.getDocument()));
or = update(this._systemDB, "activities", query, value);
just the 2nd set will be applied.
Is there any way do avoid the double execution and apply the update with just one call?
Basic rule of "hash/map" objects is that you can only have one key. It's the "highlander" rule ( "There can be only one" ) applied in general reason. So just apply differently:
BasicDBObject value = new BasicDBObject();
value.put("$set",
new BasicDBObject("transactions.$.date_execution",new Date())
.add( new BasicDBObject("transactions.$.result",m_OR.getDocument() )
);
So basically "both" field arguments are part of the "$set" statement as in the serialized form:
{
"$set": {
"transactions.$.date_execution": new Date(),
"transactions.$.result": m_Or.getDocument()
}
}
Which is basically what you want in the end.
Your suggestion was right, just had to fix a little the syntax this way:
BasicDBObject value = new BasicDBObject();
value.put("$set",
new BasicDBObject("transactions.$.date_execution",new Date())
.append("transactions.$.result",m_OR.getDocument())
);
This worked perfectly ;)
Thanks!
Samuel

How to add only embedded fields repetitively in mongodb using Java?

Given the JSON:
{
"_id" : 2,
"Act_Name" : "prashanth",
"Act_Alias" : "H C",
"Group_Account_Name" : "Prashan",
"OpeningBalance" : 10000,
"Dr_Cr" : 10000,
"Is_Reserved" : true,
"Is_Group_Act" : false,
"Contact_Name" : "Prashanth",
"Contact_Address" : "Bangalore",
"Cr_Limit" : 2000,
"isDeleted" : true,
"Cr_Days" : {
"BillwiseTracking" : {
"Reference_Number" : 123,
"Reference_Date" : null,
"Due_Date" : null,
"Amount" : 12334,
"Ref_Dr_Cr" : 12
}
}
}
I would like to repetitively add only the fields:
"Reference_Number" : 123,
"Reference_Date" : null,
"Due_Date" : null,
"Amount" : 12334,
"Ref_Dr_Cr" : 12
My code is follows.
BasicDBObject billwiseTracking = new BasicDBObject();
billwiseTracking.put("Reference_Number",referenceNumber);
billwiseTracking.put("Reference_Date", referenceDate);
billwiseTracking.put("Due_Date", dueDate);
billwiseTracking.put("Amount", amount);
billwiseTracking.put("Ref_Dr_Cr", refDrCr);
BasicDBObject updateObj = new BasicDBObject("BillwiseTracking", billwiseTracking);
accountHeads.update(findQuery, new BasicDBObject("$set", updateObj));
i am not properly understand your Question but if you want to append
first
"BillwiseTracking" object under "Cr_Days"
then structure should like this
"Cr_Days" :
{
"BillwiseTracking" :
[
{
"Reference_Number" : 123,
"Reference_Date" : null,
"Due_Date" : null,
"Amount" : 12334,
"Ref_Dr_Cr" : 12
},
{
"Reference_Number" : 145,
"Reference_Date" : null,
"Due_Date" : null,
"Amount" : 12355,
"Ref_Dr_Cr" : 13
}
]
}
it means it should be array element and every time you have to append for "BillwiseTracking" value
this may be your Solution
// query for append data ele.
BasicDBObject updateQuery = new BasicDBObject();
updateQuery.put( "_id", 2 );
// object to appen
BasicDBObject billwiseTracking = new BasicDBObject();
billwiseTracking.put("Reference_Number",referenceNumber);
billwiseTracking.put("Reference_Date", referenceDate);
billwiseTracking.put("Due_Date", dueDate);
billwiseTracking.put("Amount", amount);
billwiseTracking.put("Ref_Dr_Cr", refDrCr);
// push used to append data elements
BasicDBObject updateCommand = new BasicDBObject();
updateCommand.put( "$push", new BasicDBObject( "BillwiseTracking", billwiseTracking ) );
// final query execution
WriteResult result = shoppingLists.update( updateQuery, updateCommand, true, true );

MongoDB Query in Array of Arrays and add if not present

I have
{
"Districts" :
[{ "name" : "Krishna"
, "Locations" : [{ "name" : "Vijayawada"}
,{ "name" : "Machilipatnam"}]}
, { "name" : "Guntur"
, "Locations" : [{ "name" : "Satenpalli"}]}
]
, "_id" : 1
, "name" : "Andhra Pradesh"
}
I am trying to create one more Location "Achampet" if District name is "Guntur" so the result should be this below. The result should be the same even if I try to add Achampet more than once.
{
"Districts" :
[{ "name" : "Krishna"
, "Locations" : [{ "name" : "Vijayawada"}
,{ "name" : "Machilipatnam"}]}
, { "name" : "Guntur"
, "Locations" : [{ "name" : "Satenpalli"}
,{ "name" : "Achampet"}]}
]
, "_id" : 1
, "name" : "Andhra Pradesh"
}
But my java code doesn't work
DBObject newLoc = new BasicDBObject("Districts", new BasicDBObject("name", distName).append("Locations", new BasicDBObject("name", locName)));
if (statesColl.findOne(newLoc) == null) {
DBObject updateLoc = new BasicDBObject("$push", newLoc);
statesColl.update(queryDist, updateLoc);
}
It is creating a new District everytime I try to add a location. How can I fix this?
This is how you can do it using the $ positional operator in Java:
...
DBObject selectQuery = new BasicDBObject("_id", 1); // Matches the document
selectQuery.append("Districts.name", distName); // Matches the element in the array where District name = Guntur
BasicDBObject updateFields = new BasicDBObject();
updateFields.put("Districts.$.Locations", new BasicDBObject("name":"Achampet"));
DBObject updateQuery = new BasicDBObject("$addToSet", updateFields);
statesColl.update(selectQuery, updateQuery);
...

Categories

Resources