How to pass BasicDBList of $OR criteria to QueryBuilder? - mongodb

My app is dynamically generating a mongo query via a QueryBuilder. I'm trying to make it so that the passed text can be compared against several fields in my collection. I need to iterate through data in order to know what to add to the $OR ( 'text_to_compare' vs title field, 'text_to_compare vs body field), so I've been trying to store each section of the $OR clause in a BasicDBList as follows:
BasicDBObject orsDBObject = new BasicDBObject();
BasicDBList orsList = new BasicDBList();
for( String dbFieldNameInstance: listDBFieldName ){
QueryBuilder innerQB = new QueryBuilder();
innerQB.put(dbFieldNameInstance).is(obj.getValue()); //using this inner QueryBuilder to assist in creating the document
orsList.add(innerQB.get());
}
orsDBObject.putAll(orsList); //put each of the BasicDBList items into a BasicDBObject
qb.or(orsDBObject); //finally, add the entire $OR clause to my bigger querybuilder
System.out.println("qb: " + qb.get());
It seems like I might be jumping through more conversions than are necessary. It isn't working either. The output from the above ends up looking like this:
qb: { "$or" : [ { "0" : { "primary_industries_names" : "electrical"} , "1" : { "primary_industries_aliases" : "electrical"} , "2" : { "secondary_industries_names" : "electrical"} , "3" : { "secondary_industries_aliases" : "electrical"} , "4" : { "product_experience_names" : "electrical"} , "5" : { "product_experience_aliases" : "electrical"}}]}
For the most part it looks right to me, however I don't think the "0": should be appearing in there. Any advice would be greatly appreciated.

You kind of have that the wrong way around. Not re-writing your code, but you should get the idea:
// Before the loop
QueryBuilder qb = new QueryBuilder();
// And loop things like this
for ( ) {
qb.or(new BasicDBObject("this","that"));
}
// After loop
System.out.println(qb.get());
If you are trying to nest several conditions then you seem to be missing the $and operator. Consider the following:
QueryBuilder qb = new QueryBuilder();
for (int x=0; x<2;x++) {
qb.or(new BasicDBObject("this","that"));
}
QueryBuilder qb2 = new QueryBuilder();
qb2.and(qb.get());
QueryBuilder qb3 = new QueryBuilder();
for (int x=0; x<2;x++) {
qb3.or(new BasicDBObject("something", "else"));
}
qb2.and(qb3.get());
System.out.println(qb2.get());

Related

Mongo DB Update to a sub array document

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);

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;
}

Remove an array entry in an object using spring data mongodb

I recently spent some time trying to use the $pull operator through Spring's Data MongoOperations interface, so I thought it would be nice to share my findings in case anyone bumps into a similar problem.
So here it goes...
I have 2 java POJOs like so :
#Document
public class OutterObject{
private String id;
private String name;
private List<InnerDocument> innerDocs;
//SETTERS - GETTERS Ommited
public class InnerDocument{
private String id;
private String name;
//SETTERS - GETTERS Ommited
This is stored in a Mongo collection like so :
"_id" : "doc2",
"_class" : "OutterObject",
"name" : "doc2",
"innerDocs" : [{
"_id" : "innerDoc21",
"name" : "innerDoc21"
}, {
"_id" : "innerDoc22",
"name" : "innerDoc22"
}]
I'm trying to use the $pull operator in order to remove all objects inside the innerDoc collection having a name value = "innerDoc22".
I know how to accomplish this using the mongo driver like so :
List<String> ids =
Arrays.asList("innerDoc22");
BasicDBObject find = new BasicDBObject();
match.put("innerDocs.name",
BasicDBObjectBuilder.start("$in", ids).get());
BasicDBObject update = new BasicDBObject();
update.put(
"$pull",
BasicDBObjectBuilder.start("innerDocs",
BasicDBObjectBuilder.start("name", "innerDoc22").get()).get());
DBCollection col= mongoOperations.getDb().getCollection("outterObject");
col.update(find , update);
I'm trying to accomplish the same thing using Spring's MongoOperations Interface.
Here is my code using the MongoOperations interface :
List<String> ids = Arrays.asList("innerDoc22");
Query removeQuery = Query.query(Criteria.where("innerDocs.name").in(ids));
WriteResult wc = mongoOperations.upsert(
removeQuery,
new Update().pull("innerDocs.name", "innerDoc22"),
OutterObject.class);
System.out.println(wc.getLastError());
I'm not getting any errors when calling getLastError() the update is simply not done in the database.
I know a similar question has already been asked here but the answer that was given does not use the MongoOperations interface.
After searching a bit and looking at the source code I realized that I needed to pass an InnerDocument object as a second parameter to the pull method so that the spring classes would be able to do the mapping correctly.
As it turns out I can navigate objects while selecting objects (I'm using "innerDocs.name" in the removeQuery) but I cannot (or havent found a way) do the same when updating a document.
Below is how I implemented the query using MongoOperations :
List<String> ids = Arrays.asList("innerDoc22", "innerDoc21");
Query removeQuery = Query.query(Criteria.where("innerDocs.name").in(ids));
WriteResult wc =
mongoOperations.upsert(removeQuery,
new Update().pull("innerDocs",
new InnerDocument("innerDoc22", null)),
OutterObject.class);
System.out.println(wc.getLastError());
You can also use the BasicDBObject instead of the InnerDocument I found this out by accident. By printing out the update object, you can see the actual mongo shell query json, which is super helpful for debugging.
:
Update updateObj = new Update()
.pull("innerDocs", new BasicDBObject("innerDocs.name","innerDoc22"));
System.out.println("UPDATE OBJ: " + updateObj.toString());
results in:
UPDATE OBJ: { "$pull" : { "innerDocs" : { "innerDocs.name" : "innerDoc22"}}}
I tried the solution given by med116 and I had to modify it to work :
Update updateObj = new Update().pull("innerDocs", new BasicDBObject("name","innerDoc22"));
Because otherwise there was no matching object in my array.
in spring data mongo,just like this:
//remove array's one elem
UpdateResult wc = mongoTemplate.upsert(removeQuery,new Update().pull("tags",Query.query(Criteria.where("tag").is("java"))),TestPull.class);
//remove array's multi-elem
UpdateResult wc1 = mongoTemplate.upsert(removeQuery,new Update().pull("tags",Query.query(Criteria.where("tag").in(Arrays.asList("mongo", "netty")))),TestPull.class);
If you simply want to remove an element from array which does not have any other property like name then write the query you wish and
Update update = new Update();
update.pull("Yourarrayname","valueyouwishtodelete");
mongoTemplate.upsert(query,update,"collectionname");
That's my solution - thanks to #ufasoli:
public Mono<ProjectChild> DeleteCritTemplChild(String id, String idch) {
Query query = new Query();
query.addCriteria(Criteria
.where("_id")
.is(id)
.and("tasks._id")
.is(idch)
);
Update update = new Update();
update.set("tasks.$", null);
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
);
}
This works for me!
UpdateResult updateResult = mongoTemplate.updateMulti(new Query(where("id").is(activity.getId())), new Update().pull("comments", new Query(where("id").is(commentId))), Activity.class);
It will generate:
Calling update using query: { "_id" : { "$oid" : "61f900e7c7b79442eb3855ea"}} and update: { "$pull" : { "comments" : { "_id" : "61fac32e3f9ab5646e016bc8"}}} in collection: activity

Mongo DB query in java

I have to write a complex mongo query using java but am not able to do it.
The mongo query looks like this:
db.video.findOne( {
$or: [
{ key1: { $in : [764] } },
{ key2: {$in : [list2] } },
{ $and [ { key2 : 3}, {key4:67} ] }
]
})
I have to write the above query using the QueryBuilder class. In what way can I do it?
Thanks
Using QueryBuilder your query should look like this
DBObject query = QueryBuilder.start().or(
QueryBuilder.start("key1").in(764).get(),
QueryBuilder.start("key2").in(keys).get(),
QueryBuilder.start().and("key3").is(3).and("key4").is(64).get()
).get();
Consider using jongo (an API over mongo-java-driver) you can simply copy/paste queries from the shell :
collection.findOne("{$or:[{key1: {$in:[764]}},{key2:{$in:[#]}}, {$and:[{key3:3},{key4:67}]}]}", keys).as(People.class);
I had the same problem and i got a solution in another way :
ArrayList orList = new ArrayList();
ArrayList andList = new ArrayList();
orList.add(new BasicDBObject("key1", new BasicDBObject("$in", 764)));
orList.add(new BasicDBObject("key2", new BasicDBObject("$in", list2)));
andList.add(new BasicDBObject("key2", 3));
andList.add(new BasicDBObject("key4", 67));
orList.add(new BasicDBObject("$and", andList));
BasicDBObject query = new BasicDBObject("$or", orList);