I have the following document in MongoDb collection called hosts
{ "_id" : ObjectId("532aeec040a83df82181ff3c"),
"os" : "WINDOWS",
"name" : "Host 1",
"bas" : [
{ "wwn" : "EE:00:11:FF", "id" : "1" },
{ "wwn" : "AA:00:11:BB", "id" : "2" } ]
}
Now I want to update ba of id=1 in Host of name=Host 1 to { "wwn" : "AA:BB:CC:DD", "id" : "11" }. After the update the result will be
{ "_id" : ObjectId("532aeec040a83df82181ff3c"),
"os" : "WINDOWS",
"name" : "Host 1",
"bas" : [
{ "wwn" : "AA:BB:CC:DD", "id" : "11" },
{ "wwn" : "AA:00:11:BB", "id" : "2" } ]
}
For this I used the mongo shell command
db.hosts.update( { name : "Host 1", "bas.id" : "1" },
{ "$set" : { "bas.$" : { wwn : "AA:BB:CC:DD", id : "11"} }} );
which worked great. Now I wanted to do the same using Java drivers and here is my code
BasicDBObject example = new BasicDBObject("name", "Host 1").append("bas\uff0eid", "1");
BasicDBObject newValue = new BasicDBObject("\u0024set", new BasicDBObject("bas\uff0e\u0024", new BasicDBObject().append("wwn", "AA:BB:CC:DD).append("id","11")) ;
collection.update(example, newValue);
In the above code I had to replace . with \uff0 and $ with \u0024 to avoid exception being thrown. This update call is not updating the collection. When I inspected the BasicDBObject.toString() the JSON were same as what I had typed on console. Please help me.
I don't understand why you should need to write $ and . as Unicode escapes. I tried running your code with the actual characters in them and it worked fine with the 2.11 Java driver.
But your code has two problems: 1) the full stop . is \u002e, not \uff0e, which is a very different character. 2) the syntax of the last part of the statement is not correct, but that is probably a copying error.
Here's the code that I used, and it worked fine (I only created the objects and didn't actually run it against the database but I don't see why that wouldn't work).
BasicDBObject example = new BasicDBObject("name", "Host 1").append("bas.id", "1");
BasicDBObject newValue = new BasicDBObject("$set",
new BasicDBObject("bas.$",
new BasicDBObject()
.append("wwn", "AA:BB:CC:DD").append("id","11"))) ;
https://github.com/mongodb/mongo-java-driver/blob/master/src/main/com/mongodb/DBCollectionImpl.java#L249
If your value object doesn't have a key starting with $ then it will check the embedded document for illegal characters. Can you post your original code that had this problem, without the escapes?
Related
I recently switched from java mongo driver 3.1 to 3.4 in attempts to use Cosmo db api for mongo on azure. I am having issues with the driver when attempting to modify and field within an array.
My mongo Object looks like so
{
"_id" : ObjectId("5af4d4e97bad4700076d7aea"),
"URI" : "v3egoun#myip"
},
"record" : false,
"active" : true,
"audioonly" : true,
"environment" : "dev",
"participants" : [
{
"user" : "test1.medocity#test.org",
"state" : "pending",
"callid" : "null",
"host" : true
},
{
"user" : "test2.medocity#test.org",
"state" : "pending",
"callid" : "74ff79f83c375355058838a1b8d0ec03#test.org",
"host" : false
}
]
}
I wish to modify the variables for state and callid within the array for the user matching the query:
BasicDBObject query = new BasicDBObject();
query.put("URI", URI);
query.put("participants.user", FROMURI);
BasicDBObject data = new BasicDBObject();
data.put("participants.$.state", newstate.toString());
data.put("participants.$.callid", XMScallid);
BasicDBObject command = new BasicDBObject();
command.put("$set", data);
table.updateOne(query, command);
As of now I get the following error.
Exception in thread "Thread-13" com.mongodb.MongoWriteException: Invalid BSON field name 'participants.$.state'
Anyone have an Idea of how I can modify the state within the participants array for the user provided in the query?
Thanks
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.
So I have a few dbobjects in my mongo database. Here's an example of one of the objects:
{ "_id" : { "$oid" : "525b048580c3fb0d62d2b6fc"} , "city" : "London" , "currentWeather" : [ { "cloudcover" : "25" , "humidity" : "82" , "observation_time" : "08:37 PM" , "precipMM" : "0.0" , "pressure" : "1008" , "temp_C" : "11" , "temp_F" : "52" , "visibility" : "10" , "weatherCode" : "113" , "weatherDesc" : [ { "value" : "Clear"}] , "weatherIconUrl" : [ { "value" : "http://cdn.worldweatheronline.net/images/wsymbols01_png_64/wsymbol_0008_clear_sky_night.png"}] , "winddir16Point" : "W" , "winddirDegree" : "280" , "windspeedKmph" : "19" , "windspeedMiles" : "12"}]}
Now, I need to get all the dbobjects in my database whose value is lower than a given "temp_C", I have used something like this:
BasicDBObject query = new BasicDBObject("temp_C", new BasicDBObject(">", graden));
But it's failing, and I think it is because the property is a subproperty of "currentWeather", yet I have no idea how to address this problem. I am using java to do this.
Looking at your document structure, you're trying to access a subdocument that lives inside an array in your document, so it's a bit more complicated than a standard query:
{ "_id" : { "$oid" : "525b048580c3fb0d62d2b6fc"} , <-- Document
"city" : "London" ,
"currentWeather" : [ <-- Array
{ "cloudcover" : "25", <-- Sub document
...etc...
"pressure" : "1008" ,
"temp_C" : "11",
"temp_F" : "52",
...etc...
}
]
}
In order to get to the nested object, you need to reference its position in the array (in this case, it's zero as it's the first element in the array) and then the field name in the sub document. So your query looks like this:
BasicDBObject query = new BasicDBObject("currentWeather.0.temp_C",
new BasicDBObject("$gt", 11));
Note you had two problems in your original query:
1) You need to reference currentWeather.0.temp_C
2) Your gt operator needs to start with a dollar sign not an ampersand.
Also, you said you wanted the query to return values lower than a given value, in which case you probably want $lt not $gt.
You can't directly use the value of the object of an array in a query. You can use aggregate framework of Mongo. Java Docs For Aggregate are here
I have a string as:
'"startDate" : {"\\$gte" : new Date() }'
I actually want to query my mongodb database collection for all the collections with dates after the present date.Here is a sample document:
{
"_id" : ObjectId("51e2a857adc0c2fb535f6904"),
"dateInfo" : {
"dates" : {
"startDate" : ISODate("2013-07-13T04:00:00Z"),
"endDate" : ISODate("2013-07-19T20:00:00Z")
},
"named" : "name1",
"fieldX" : "field1",
"contact" : {
"numbs" : ["+44 121 127 127", "+44 568 789 256", "+44 687 5788 9875"]
}
},
"Locality" : "locality1",
"type" : "ewhet"
}
from mongo shell, I am able to query it as follows:
db.collectionName.find({"dateInfo.dates.startDate" : {"$gte" : new Date()}})
Now I want to do it from groovy. I am trying to do it as(using mongoDB java api):
DBCursor cursor = db.collectionName.find(new JsonSlurper().parseText('{"startDate" : {"\$gte" : new Date() }}'))
Above code gives error as:
Lexing failed on line: 1, column: 48, while reading 'new ', was trying to match the constant 'null'
Now the problem with JsonSlurper is that it requires double quotes in both key and value names.But in case I put a double quote in new Date(), it wont be evaluated by mongodb.
So what can be done here?
Have you tried:
db.collectionName.find( [ 'dateInfo.dates.startDate' : ['$gte' : new Date() ] ] )
I have a little bug with my script:
BasicDBObject change = new BasicDBObject();
BasicDBObject account =
new BasicDBObject().append("$set", new BasicDBObject().append("status", 0));
account.append("pos.X", getX());
account.append("pos.Y", getY());
account.append("pos.Z", getZ());
change.append("pseudo", gPlayer);
coll.update(change, account);
And the structure of mongoDB is :
{
pseudo: "pseudo"
email: "email"
password: "password"
status: "1"
pos: [
{X: "90.45}
{Y: "90.45}
{Z: "90.45}
]
}
But this is not working! No value is modified.
Thanks for your help.
Java is a very verbose language, and sometimes it is easiest to first write your query using the JS shell, make sure it performs as desired, and then translate it into Java.
By adding System.out.println(account.toString()) to your Java code, I can see that your Update document looks like the following, which is not valid:
{ "$set" : { "status" : 0} , "pos.X" : "90.45" , "pos.Y" : "90.45" , "pos.Z" : "90.45"}
From your question, it is not entirely clear what you would like your updated document to look like, but I am guessing that you would like to modify the values of "status" and "pos.0.X", "pos.1.Y", and "pos.2.Z". Because X, Y, and Z are all stored as individual embedded documents inside an array, they will have to be referenced by their positions in order to be updated at the same time. If possible, you might find it preferable to rearrange your document structure such that X, Y, and Z are all stored inside the same document, like so:
"pos" : {
"X" : "0",
"Y" : "0",
"Z" : "0"
}
This way, you will be able to access each variable more easily using dot notation, pos.X, pos.Y, pos.Z, which from your post looks like what you were intending. More information on embedded documents may be found in the "Dot Notation (Reaching into Objects)" documentation:
http://www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29
Here is an Update statement that will modify the above values:
> db.pseudo.find({ "pseudo" : "gPlayer"}).pretty()
{
"_id" : ObjectId("4f904ebb5bebd4375b759c90"),
"email" : "email",
"password" : "password",
"pos" : [
{
"X" : "90.45"
},
{
"Y" : "90.45"
},
{
"Z" : "90.45"
}
],
"pseudo" : "gPlayer",
"status" : "1"
}
> db.pseudo.update({"pseudo" : "gPlayer"}, { "$set" : { "status" : 0 , "pos.0.X" : "0" , "pos.1.Y" : "0" , "pos.2.Z" : "0"}})
> db.pseudo.find({ "pseudo" : "gPlayer"}).pretty()
{
"_id" : ObjectId("4f904ebb5bebd4375b759c90"),
"email" : "email",
"password" : "password",
"pos" : [
{
"X" : "0"
},
{
"Y" : "0"
},
{
"Z" : "0"
}
],
"pseudo" : "gPlayer",
"status" : 0
}
>
Translated into Java this is:
BasicDBObject change = new BasicDBObject("pseudo", "gPlayer");
BasicDBObject setDoc = new BasicDBObject();
setDoc.append("status", "0");
setDoc.append("pos.0.X", "0");
setDoc.append("pos.1.Y", "0");
setDoc.append("pos.2.Z", "0");
BasicDBObject account = new BasicDBObject("$set", setDoc);
coll.update(change, account);
I realize that I guessed a little bit about exactly the update that you would like to do, but hopefully the above will get you pointed in the right direction!