Mongo's Java driver, how to build a complex query - java

I'm testing against a database with one document in it. With the following code I get the following result from the database
BasicDBObject keys = new BasicDBObject();
keys.put("symbol", 1 );
keys.put("price", 1 );
keys.put("Exchange", "SH");
keys.put("NumShares", new BasicDBObject("$gte", 0) );
BasicDBObject empty = new BasicDBObject();
DBCursor cursor_02 = coll_tmp.find( empty, keys );
System.out.println( "<2> " + cursor_02.count());
while(cursor_02.hasNext()) {
System.out.println( "<2> " + cursor_02.next());
}
This will print out the following data
<2> { <id & oid> , "symbol" : "ESLR" , "Exchange" : "SH"}
The document also has a "NumShares" field set to a value of 2.34508872. Wanting to try out the $gt, $gte, $lt, $lte, and $ne operators, I add in a line of code after the last 'keys.put()' line.
keys.put("Price", new BasicDBObject("$gte", 0) );
This code should continue to select the lone document, as it's NumShares field is indeed greater than or equal to zero. Instead I get the following error.
Can't canonicalize query: BadValue Unsupported projection option: NumShares: { $gte: 0 }
The goal ultimately is to have a MongoDB equivalent of the SQL query "SELECT price, symbol, exhange FROM stock WHERE NumShares >= 0, etc..."
I realize that mkyong doesn't cover this particular query structure in his tutorial, so I'm asking you all. Thanks

You want to put your criteria in the query, not in the list of keys you want to return:
BasicDBObject keys = new BasicDBObject(); // will specify the returned fields (SELECT)
keys.put("symbol", 1 );
keys.put("price", 1 );
keys.put("Exchange", 1);
BasicDBObject query = new BasicDBObject(); // will select the documents you want (WHERE)
query.put("numShares", new BasicDBObject("$gte", 0) );
DBCursor cursor_02 = coll_tmp.find( query, keys );
System.out.println( "<2> " + cursor_02.count());
while(cursor_02.hasNext()) {
System.out.println( "<2> " + cursor_02.next());
}

You need to put your result-restricting query arguments to your first Object of the query. The second Object is a Projection of the result. It can only specify the tags to be displayed (as true/1 or false/0) there are no other restrictions like "$lte" allowed. Putting it to the query object like in the following should work:
BasicDBObject keys = new BasicDBObject();
keys.put("symbol", 1 );
keys.put("price", 1 );
keys.put("Exchange", 1);
BasicDBObject search = new BasicDBObject();
search.put("Exchange", "SH");
search.put("numShares", new BasicDBObject("$gte", 0) );
DBCursor cursor_02 = coll_tmp.find(search, keys);
Also MongoDBs keys of documents are case-sensitive. So if your key is "NumShares" you cannot find it by looking for "numShares".

Related

How to take the filter value from mongodb in java code

I am updating mongodb item in collection using the below command.
Now i want to take the set and filter value in java.
db.practice.update({"name" : "testmysqlupdate"},{$set:{"name" : "5"}})
I am able to take the value which need to be update i.e. name : 5, but not the conditional value i.e. "name" : "testmysqlupdate"
static ChangeStreamDocument<BasicDBObject> dbOb;
static MongoCursor<ChangeStreamDocument<BasicDBObject>> cursor;
cursor = collection.watch().fullDocument(FullDocument.UPDATE_LOOKUP).iterator();
System.out.println("value of update stream us " + dbOb.getUpdateDescription().getUpdatedFields().toString());
System.out.println("Id Value is " + dbOb.getDocumentKey().toString());
document = dbOb.getUpdateDescription().getUpdatedFields().toString();
BasicDBObject searchQuery = new BasicDBObject();
searchQuery.put("name", "testmysqlupdate");
BasicDBObject newDocument = new BasicDBObject();
newDocument.append("$set", new BasicDBObject().append("name", "5"));
collection.update(searchQuery, newDocument);
Try this to update a field in MongoDB.

How can i groupby a column in Mongodb using java? [duplicate]

I'd like to learn how to implement group by query using Mongo DB Java 3.x Driver. I want to group my collection through the usernames, and sort the results by the count of results DESC.
Here is the shell query which I want to implement the Java equivalent:
db.stream.aggregate({ $group: {_id: '$username', tweetCount: {$sum: 1} } }, { $sort: {tweetCount: -1} } );
Here is the Java code that I have implemented:
BasicDBObject groupFields = new BasicDBObject("_id", "username");
// count the results and store into countOfResults
groupFields.put("countOfResults", new BasicDBObject("$sum", 1));
BasicDBObject group = new BasicDBObject("$group", groupFields);
// sort the results by countOfResults DESC
BasicDBObject sortFields = new BasicDBObject("countOfResults", -1);
BasicDBObject sort = new BasicDBObject("$sort", sortFields);
List < BasicDBObject > pipeline = new ArrayList < BasicDBObject > ();
pipeline.add(group);
pipeline.add(sort);
AggregateIterable < Document > output = collection.aggregate(pipeline);
The result I need is the count of documents grouped by username. countOfResults returns the total number of the documents the collection has.
You should try not to use old object (BasicDBObject) types with Mongo 3.x. You can try something like this.
import static com.mongodb.client.model.Accumulators.*;
import static com.mongodb.client.model.Aggregates.*;
import static java.util.Arrays.asList;
Bson group = group("$username", sum("tweetCount", 1));
Bson sort = sort(new Document("tweetCount", -1));
AggregateIterable <Document> output = collection.aggregate(asList(group, sort));

Mongo Java Driver - How to update a subdocument into an array element

How could I update a specific field in subdocument of array element ?
My question is similar to the following below, but in my case I need to update just a subdocument value.
MongoDB: How do I update a single subelement in an array, referenced by the index within the array?
I have the following documento model :
{
_id : "xpto",
other_stuff ... ,
templates : [
{
templateId:"template-a"
body: {
en_US:"<p>Hello World!</p>"
}
},
{
templateId:"template-b"
body: {
es_ES:"<p>Holla !</p>"
}
}
]
}
So, In mongodb shell the following statement works perfectly for me:
db.apiClient.update({"_id":"xpto","templates.templateId":"template-b"}, {$set:{"templates.$.body.es_ES":"<h1>Gracias !</h1>"}})
However , when i try to do it with Mongo Java Driver , I get an IllegalArgumentException.
BasicDBObject selectQuery = new BasicDBObject("_id", "xpto");
selectQuery.put("templates.templateId", "template-b");
BasicDBObject updateQuery = new BasicDBObject();
for(String locale : template.getBody().keySet()) {
String updateBodyLocaleExpression = new StringBuilder()
.append("templates.$.body.").append(locale).toString();
String updateBodyLocaleValue = template.getBody().get(locale);
updateQuery.put(updateBodyLocaleExpression, updateBodyLocaleValue);
}
updateQuery.put("$set", updateQuery);
getCollection(COLLECTION_NAME).update(selectQuery, updateQuery, true, true);
It throws the following exception :
Caused by: java.lang.IllegalArgumentException: Invalid BSON field name templates.$.body.es_ES
at org.bson.AbstractBsonWriter.writeName(AbstractBsonWriter.java:494)
at com.mongodb.DBObjectCodec.encode(DBObjectCodec.java:127)
at com.mongodb.DBObjectCodec.encode(DBObjectCodec.java:61)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
at com.mongodb.connection.RequestMessage.addDocument(RequestMessage.java:253)
at com.mongodb.connection.RequestMessage.addCollectibleDocument(RequestMessage.java:219)
at com.mongodb.connection.UpdateMessage.encodeMessageBodyWithMetadata(UpdateMessage.java:77)
at com.mongodb.connection.RequestMessage.encodeWithMetadata(RequestMessage.java:160)
at com.mongodb.connection.WriteProtocol.execute(WriteProtocol.java:85)
Is something wrong with my code ?
Thanks.
Yes. You construct wrong updateQuery. You put fields templates.$.body... in the BasicDbObject and then add same document into $set field into itself. MongoDB tries to update field templates.$.body. and here $ is the part of the field name instead of operator.
Here is working example:
//List is for testing purposes only
List<String> locales = Arrays.asList("en_US", "en_UK");
Document query = new Document("_id", "xpto")
.append("templates.templateId", "template-b");
Document updateQuery = new Document();
for (String locale : locales) {
updateQuery.put("templates.$.body." + locale, "<pre>Updated " + locale + "</pre>");
}
collection.updateOne(query, new Document("$set", updateQuery));
Document is almost the same as BasicDbObject but more general.

Getting data from mongo db like we get in mysql?

I am inserting some value into mongodb in this way.
MongoClient mongo = new MongoClient( "localhost" , 27017 );
DB db = mongo.getDB("test");
DBCollection table = db.getCollection("paramDescMapper");
String key = UUID.randomUUID().toString();
String value = "{\"param0\":\"Car Make\",\"param1\":\"Car Model\",\"param2\":\"Car Variant\",\"param3\":\"Car Year\",\"param4\":\"Car Number\"}";
JSONObject jsonObject = new JSONObject(value);
// create a document to store key and value
BasicDBObject document = new BasicDBObject();
document.put("apiKey", key);
document.put("apiParamDesc", jsonObject.toString());
table.insert(document);
It is inserting data in this way.
{ "_id" : { "$oid" : "534251125f1ab7ec747298cd"} , "apiKey" : "1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6" , "apiParamDesc" : "{\"param0\":\"Car Make\",\"param1\":\"Car Model\",\"param2\":\"Car Variant\",\"param3\":\"Car Year\",\"param4\":\"Car Number\"}"}
Now i want to get apiParamDesc value using apiKey. Like how we get data in mysql.
Select apiParamDesc where apiKey =
'1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6';
I googled a lot but could not found anything. This is how i am trying to get this apiParamDesc
BasicDBObject whereQuery = new BasicDBObject();
whereQuery.put("apiKey", "1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6");
DBCursor cursor = table.find(whereQuery);
while(cursor.hasNext()) {
System.out.println(cursor.next());
}
But this is giving me entire row. I want only apiParamDesc in a String.
Please help me.
Thanks
You can easily do it with aggregation framework. Below is the example which can resolve your issue:
// create our pipeline operations, first with the $match
DBObject match = new BasicDBObject("$match", new BasicDBObject("apiKey", "1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6"));
// build the $projection operation
DBObject fields = new BasicDBObject("apiParamDesc", 1);
fields.put("_id", 0);
DBObject project = new BasicDBObject("$project", fields );
// run aggregation
AggregationOutput output = collection.aggregate( match, project);
You can also make use of only db.coll.find(< criteria >, < projection >);
BasicDBObject query = new BasicDBObject(new BasicDBObject("apiKey", "1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6"), new BasicDBObject("apiParamDesc", 1).append("_id", 0));
//Which is equivalent to a follwoing query
//'db.coll.find({"apiKey": "1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6"}, {"apiParamDesc": 1,"_id": 0});'
cursor = coll.find(query);
One more thing to update you on "I want only apiParamDesc in a String.", is if you are storing string like
"apiParamDesc" : "{\"param0\":\"Car Make\",\"param1\":\"Car Model\",\"param2\":\"Car Variant\",\"param3\":\"Car Year\",\"param4\":\"Car Number\"}
You cannot query on those sub level fields like param0, param1 ...
You data should look like :
{
"_id":{
"$oid":"534251125f1ab7ec747298cd"
},
"apiKey":"1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6",
"apiParamDesc":{
"param0":"Car Make",
"param1":"Car Model",
"param2":"Car Variant",
"param3":"Car Year",
"param4":"Car Number"
}
}
I want only apiParamDesc in a String.
You cannot however, you can get a document (object) returned with only the apiParamDesc as its single field (my Java is rusty):
BasicDBObject whereQuery = new BasicDBObject();
whereQuery.put("apiKey", "1eb9b9e3-3af1-4b25-b7ea-1f2fcb1d9af6");
BasicDBObject fields = new BasicDBObject();
fields.put("apiParamDesc", 1);
fields.put("_id", 0);
DBCursor cursor = table.find(whereQuery, fields);
while(cursor.hasNext()) {
System.out.println(cursor.next());
}

Sorting in mongo according to number of occurences

I need to implement "Popular Keyword" in my web app so that most searched keywords will show there.. For that i have created a db in mongo. I need to retrieve data from mongo so that mostly occuring keywords should sort and should return to my page. In sql it is just like as,
select names,count(names) from keyword_db group by names order by names;
need both java code and mongo shell query..
You might want to try for the mongo shell query (I'm sorting in descending order:
db.keyword_db.aggregate ( [
{ $group: { _id: "$names", count: { $sum: 1 } } },
{ $sort: { count: -1 } } ] )
For the Java version:
DBCollection myColl = db.getCollection("keyword_db");
// for the $group operator
DBObject groupFields = new BasicDBObject( "_id", "$names");
groupFields.put("count", new BasicDBObject( "$sum", 1));
DBObject group = new BasicDBObject("$group", groupFields );
// for the $sort operator
DBObject sortFields = new BasicDBObject("count", -1);
DBObject sort = new BasicDBObject("$sort", sortFields );
// run the aggregation
AggregationOutput output = myColl.aggregate(group, sort);
System.out.println( output.getCommandResult() );
The following page may help you also:
http://docs.mongodb.org/manual/reference/sql-aggregation-comparison/
For sorting you can use following..1 for ascending and -1 for desc.
db.keyword_db.find( { names: "A" } ).sort( { user_id: 1 } )
and for groupBy You can use Aggregation framework..
http://docs.mongodb.org/manual/reference/method/db.collection.group/

Categories