Mongo Java finding Maching record in mongo array - java

My sample doc is
{
"_id" : ObjectId("51f89c1484ae3aa758ad56e9"),
"group_name" : "Operator",
"privileges" : [
"Add Group",
"Edit/Delete Group",
"Add User",
"Edit/Delete User",
"Log History",
"Add Stopwords Group",
"Edit/Delete Stopwords Group",
"Add Stopwords",
"Edit/Delete Stopwords",
"Recomended Stopwords"
],
"users" : [
{
"full_name" : "operator1",
"user_name" : "operator",
"password" : "pass$123",
"status" : "Active"
},
{
"full_name" : "Sumit Dshpande",
"user_name" : "dsumit",
"password" : "pass$123",
"status" : "Active"
}
]
}
I am trying to match user_name and password in users array entered by user.
My java code is :
BasicDBObject query = new BasicDBObject();
List<BasicDBObject> parts = new ArrayList<BasicDBObject>();
parts.add(new BasicDBObject("users.user_name", uname));
parts.add(new BasicDBObject("users.password", password));
query.put("$and", parts);
DBCursor cursor = collection.find(query);
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
But it returns all users in matching array.I want users under group so that I can change group privileges.
Is this right approach for schema designing in nosql.

We would need to get holistic information about your application in order to tell you if you're making the right modeling decisions; however, I can help you with your current query.
In order to match multiple attributes within the same element of an array, you should use the $elemMatch operator (http://docs.mongodb.org/manual/reference/projection/elemMatch/).
eg)
collection.find({users:{$elemMatch:{username:uname,password:password}}});

Related

Updating a field within matching Array in mongo

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

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.

Update a nested document filed and increment in mongodb

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
)

Updating inner doc from mongo

This is my sample doc.
{
"_id" : ObjectId("51f20148a85e39af87510305"),
"group_name" : "sai",
"privileges" : [
"Notification",
"News Letter"
],
"users" : [
{
"full_name" : "sumit",
"user_name" : "sumitdesh",
"password" : "magicmoments",
"status" : "Active"
},
{
"full_name" : "ad",
"user_name" : "asd",
"password" : "asdf",
"status" : "Active"
}
]
}
I want to replace inner doc from users array with a new doc.
This is my java code:
BasicDBObject g1=new BasicDBObject();
g1.put("full_name", "ram");
g1.put("user_name", "ram123");
g1.put("password", "pass$123");
g1.put("status", "Inactive");
BasicDBObject doc=new BasicDBObject();
doc.put("users",g1);
BasicDBObject q=new BasicDBObject("users.user_name","asd");
con.update(q,doc);
Any help is appreciated
Expected output is as follows
I want to replace inner doc with these values
{
"_id" : ObjectId("51f20148a85e39af87510305"),
"group_name" : "sai",
"privileges" : [
"Notification",
"News Letter"
],
"users" : [
{
"full_name" : "sumit",
"user_name" : "sumitdesh",
"password" : "magicmoments",
"status" : "Active"
},
{
"full_name" : "ram",
"user_name" : "ram123",
"password" : "pass$123",
"status" : "Inactive"
}
]
}
I must combine $set and $ operators, then you can update an specific item of array.
BasicDBObject g1 = new BasicDBObject();
g1.put("users.$.full_name", "ram");
g1.put("users.$.user_name", "ram123");
g1.put("users.$.password", "pass$123");
g1.put("users.$.status", "Inactive");
BasicDBObject doc = new BasicDBObject();
doc.put("$set", g1);
BasicDBObject q = new BasicDBObject("users.user_name","asd");
con.update(q,doc);
Your code will create a new document consisting only of the new user. To add a new element to an array in an existing document, use the $push operator
BasicDBObject where = new BasicDBObject("_id", new ObjectId("51f20148a85e39af87510305");
BasicDBObject doc = //... your new user object
BasicDBObject push = new BasicDBObject("$push", doc);
con.update(where, push);
To modify a field of an existing document, you can use the set-operator combined with the $-placeholder. This changes the user_name from "foo" to "bar"
BasicDBObject where = new BasicDBObject("users.user_name", "foo");
BasicDBObject value = new BasicDBObject("users.$.user_name", "bar");
BasicDBObject set = new BasicDBObject("$set", value);
con.update(where, set);
But the least headache-inducing way is to just keep the whole DBObject around when you retrieve an object from the database, mirror all modifications in the DBObject, and then call
con.save(dbObject);
An ORM wrapper library can do this for you. But while it is the easiest way, it isn't the most efficient way, because the whole document will be sent to the database. This is a problem which can be easily filed under "premature optimization" when writes are infrequent and the documents are small, but when you save often and have huge documents, it can become an issue.
This is actually fairly simple, if you follow the documentation.
The first difficulty is finding the document to update. You're looking for the user whose user_name field is 'asd', which is done, rather neatly, through the following query:
{'users.user_name': 'asd'}
The field name needs to be escaped in the mongo shell (it's a compound name) but you need not worry about that in Java.
Now that you've found your user, you need to change it. MongoDB magically stores the matched array index as $, which allows you to write the update criteria as:
{
$set: {
'users.$': {
full_name: 'ram',
user_name: 'ram123',
password: 'pass$123',
status: 'inactive'
}
}
}
You obviously know your way around the Java connector, I'll leave the transformation of my JSON object to instances of BasicDBObject to you.

Mongo java maching two or more values in mongo array

I am new to mongodb.I am trying to match username and password in collection array. My sample doc is
{
"_id" : ObjectId("51f20148a85e39af87510305"),
"group_name" : "sai",
"users" : [
{
"full_name" : "sumit",
"user_name" : "sumitdesh",
"password" : "mggg",
"status" : "Active"
},
{
"full_name" : "ad",
"user_name" : "asd",
"password" : "asdf",
"status" : "Active"
},
}
I am trying to match user name and password enter by user in above array.If user name and password match with our data,user will get login. My java code is
BasicDBObject u = new BasicDBObject("users.user_name", uname);
BasicDBObject p = new BasicDBObject("users.password", password);
f=con.coll.find(u,p);
Your document structure might get in the way here. How do you distinguish the user logging in? If you get a match, it will return the whole document, with all users.
Anyhow, to query with multiple comparisons:
BasicDBObject query = new BasicDBObject();
List<BasicDBObject> parts = new ArrayList<BasicDBObject>();
parts.add(new BasicDBObject("users.user_name", uname));
parts.add(new BasicDBObject("users.password", password));
query.put("$and", parts);
DBCursor cursor = collection.find(query);
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
You need to use the $elemMatch query operator.
Something like this should work:
BasicDBObject elementQuery = new BasicDBObject("user_name", uname);
element.put("password", password);
BasicDBObject query = new BasicDBObject("users",
new BasicDBObject("$elemMatch", elementQuery);
f=con.coll.find(query);
Rob.
P.S. On a side note - having all of the users in a single document is probably not optimal. Have you considered having a separate document for each user with the user name and password?
An example of a schema that might perform better is to divide the groups and users into two collections. Each user will have a document like:
{
"_id" : "sumitdesh",
"full_name" : "sumit",
"password" : "mggg",
"status" : "Active"
"groups" : [ "sai", "admin" ],
}
You will probably want to index the 'groups' if you query for all of the members of a group often.
The groups document will then look like:
{
"_id" : "sai",
"permissions" : [ "read", "write_secrets" ]
}
Index permissions.
A check for read permission for the sumitdesh user is then:
db.groups.findOne(
{
_id : { "$in" : [ "sai", "admin" ] } ,
"permissions" : "read"
}
);
Note that the array for the '$in' same as the array of groups from 'sumitdesh' user document.
P.P.S. ...and when you say password you mean a hash (SHA-2?) of the salted password? Right?

Categories

Resources