java mongodb 3.0: find value in internal array of documents - java

I'm using java and mongodb v.3.0.7.
I have a list of player with internal array of games with scores. This is a test that insert a document:
public void insertPlayer(String id_device) throws ParseException{
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
db.getCollection("player").insertOne(
new Document()
.append("nikname", "Guest")
.append("password", "Guest")
.append("my_id", "")
.append("id_device", id_device)
.append("language", "Italian")
.append("games", asList(
new Document()
.append("gamename", "PPA")
.append("banned", false)
.append("date", format.parse("2014-10-01T00:00:00Z"))
.append("score", 11),
new Document()
.append("gamename", "Test2game")
.append("banned", false)
.append("date", format.parse("2014-01-16T00:00:00Z"))
.append("score", 17)))
);
}
To find if a player is banned from a particular game I'm doiing this:
public boolean isBanned(String id_device){
FindIterable<Document> iterable = db.getCollection("player").find(eq("id_device", "machine1"));
System.out.println(iterable.first());
List<Document> dl = (List<Document>)iterable.first().get("games");
for(int i=0;i<dl.size();i++){
Document d = dl.get(i);
System.out.println(d.getString("gamename"));
if(d.getString("gamename").equals("PPA")){
boolean ban = d.getBoolean("banned");
return ban;
}
}
There is a faster way using embedded mongodb methods that find the document:
new Document()
.append("gamename", "PPA")
.append("banned", false)
.append("date", format.parse("2014-10-01T00:00:00Z"))
.append("score", 11),
giving id_device and gamename?
thanks

To achieve that, you need to aggregate your data.
https://docs.mongodb.org/manual/aggregation/
Depending on your usecase, aggregation may change (more query, more pipeline steps).
Here is your data:
{
"_id" : ObjectId("569a30a30586bcb40f7d2531"),
"my_id" : "",
"id_device" : "machine1",
"language" : "Italian",
"games" : [
{
"gamename" : "PPA",
"banned" : true,
"date" : ISODate("2014-10-01T00:00:00.000Z"),
"score" : 11
},
{
"gamename" : "Test2game",
"banned" : false,
"date" : ISODate("2014-01-16T00:00:00.000Z"),
"score" : 17
}
]
}
I assume:
You have a list of unique players. Each of them play unique games.
(Example: player1 never plays PPA twice)
So, you need to search a game where the player is banned and return
all information for that game.
The aggregation would be:
db.player.aggregate([
{$match:{ "id_device" : "machine1"}},
{$unwind: "$games"},
{$match:{ "games.gamename" : "PPA", "games.banned" : true}}
])
Result
[
{
"_id" : ObjectId("569a30a30586bcb40f7d2531"),
"my_id" : "",
"id_device" : "machine1",
"language" : "Italian",
"games" : {
"gamename" : "PPA",
"banned" : true,
"date" : ISODate("2014-10-01T00:00:00.000Z"),
"score" : 11
}
}
]
Some difference, if your players may play same game more than once (different date), you can change your aggregation pipelines.
{
"_id" : ObjectId("569a30a30586bcb40f7d2531"),
"my_id" : "",
"id_device" : "machine1",
"language" : "Italian",
"games" : [
{
"gamename" : "PPA",
"banned" : false,
"date" : ISODate("2014-10-01T00:00:00.000Z"),
"score" : 11
},
{
"gamename" : "Test2game",
"banned" : false,
"date" : ISODate("2014-01-16T00:00:00.000Z"),
"score" : 17
},
{
"gamename" : "PPA",
"banned" : true,
"date" : ISODate("2014-04-18T00:00:00.000Z"),
"score" : 23
},
{
"gamename" : "Foo",
"banned" : true,
"date" : ISODate("2015-03-03T00:00:00.000Z"),
"score" : 2
},
{
"gamename" : "Foo",
"banned" : false,
"date" : ISODate("2015-04-28T00:00:00.000Z"),
"score" : 2
}
]
}
So to query id_device and gamename "PPA", we define our aggregation this way:
db.player.aggregate([
{$match:{ "id_device" : "machine1"}},
{$unwind: "$games"},
{$match:{ "games.gamename" : "PPA"}},
{$group: {_id:{"_id":"$_id", "my_id":"$my_id", "id_device":"$id_device","language":"$language"}, "games" : {$push:"$games"}}},
{$project: {"_id":"$_id._id", "my_id":"$_id.my_id", "id_device": "$_id.id_device", "language":"$_id.language", "games":"$games"}}
])
Result:
[
{
"_id" : ObjectId("569a30a30586bcb40f7d2531"),
"games" : [
{
"gamename" : "PPA",
"banned" : false,
"date" : ISODate("2014-10-01T00:00:00.000Z"),
"score" : 11
},
{
"gamename" : "PPA",
"banned" : true,
"date" : ISODate("2014-04-18T00:00:00.000Z"),
"score" : 23
}
],
"my_id" : "",
"id_device" : "machine1",
"language" : "Italian"
}
]
As you see, you can add/modify pipeline steps and get desired result.

Related

How to query an array in array of dictionaries in MongoDB?

I have a following mongoDB document structure -
db.menus.findOne()
{
"_id" : ObjectId("5cf25412326c3f4f26df039b"),
"restaurantId" : "301728",
"items" : [
{
"itemId" : "CEBM4H41JR",
"name" : "Crun Chicken",
"imageUrl" : "",
"price" : 572,
"attributes" : [
"Tasty",
"Spicy"
]
},
{
"itemId" : "53Q0XS3HPR",
"name" : "Devils Chicken",
"imageUrl" : "",
"price" : 595,
"attributes" : [
"Gravy",
"Salty"
]
}
]
}
I am trying to write a query to get all the menus based on the "attributes" field under "items" in the document.
I have done the following to get the menus if "name" of "items" is given and I am getting a result -
db.menus.find({ 'items' : {$elemMatch : {'name' : {$regex : "Chicken Thali", $options: 'i' }}}}).pretty()
I have tried this for getting the result for attributes but this is not working -
db.menus.find({'items' : {$elemMatch : {'attributes' : {$all : [{$regex : "Tasty", $options: 'i' }]}}}})
How do I get the list and I also want to write this query for mongoRepository in a spring boot application?
Further, based on the restaurantId's obtained, I have to query restaurant collection in order to find all the restaurants in restaurants collection having the following structure -
{
"_id" : ObjectId("5cf2540e326c3f4f26de93dd"),
"restaurantId" : "301728",
"name" : "Desire Foods",
"imageUrl" : "https://b.zmtcdn.com/data/pictures/8/301728/d690ccb500d746530f56e1d637949da2_featured_v2.jpg",
"latitude" : 28.4900591,
"longitude" : 77.3066401,
"attributes" : [
"Chinese",
" Fast Food",
" Bakery"
],
"opensAt" : "09:30",
"closesAt" : "22:30"
}
Is the whole operation possible in a single query?
I think you can modify your query to use $in instead of $all.
To achieve your intended result, you can try:
db.collection.aggregate([
{
"$match": {
"items": {
"$elemMatch": {
"attributes": {
"$in": [
"Tasty"
]
}
}
}
}
},
{
"$lookup": {
"from": "restaurant",
"localField": "restaurantId",
"foreignField": "restaurantId",
"as": "restaurants"
}
},
{
"$unwind": "restaurants"
},
{
"$replaceRoot": { "newRoot": "$restaurants" }
}
])
Use $match at appropriate stages as needed to limit the documents pulled in memory

mongodb update not working on nested subdocument

My mongodb records are like in this link Updating nested array inside array mongodb and sample records are as below and want to update a field in the nested document "parameter" array provided it satisfies some conditions (_id : "04", operations._id : "100" and operations.parameters.pid : "012"), this update query UPDATES wrong nested record (operations.parameters.pid : '011') , please help where I am going wrong:
{
"_id" : "04",
"name" : "test service 4",
"id" : "04",
"version" : "0.0.1",
"title" : "testing",
"description" : "test",
"protocol" : "test",
"operations" : [
{
"_id" : "99",
"oName" : "test op 52222222222",
"sid" : "04",
"name" : "test op 52222222222",
"oid" : "99",
"description" : "testing",
"returntype" : "test",
"parameters" : [
{
"oName" : "Param1",
"name" : "Param1",
"pid" : "011",
"type" : "582",
"description" : "testing",
"value" : "",
"version" : 1.0
},
{
"oName" : "Param2",
"name" : "Param2",
"pid" : "012",
"type" : "58222",
"description" : "testing",
"value" : "",
"version" : 2.0
}
]
},
{
"_id" : "100",
"oName" : "test op 909090",
"sid" : "05",
"name" : "test op 90909",
"oid" : "1009",
"description" : "testing",
"returntype" : "test",
"parameters" : [
{
"oName" : "Param1",
"name" : "Param1",
"pid" : "011",
"type" : "582",
"description" : "testing",
"value" : "",
"version" : 1.0
},
{
"oName" : "Param2",
"name" : "Param2",
"pid" : "012",
"type" : "58222",
"description" : "testing",
"value" : "",
"version" : 2.0
}
]
},
{
"_id" : "101",
"oName" : "test op 52222222222",
"sid" : "04",
"name" : "test op 52222222222",
"oid" : "99",
"description" : "testing",
"returntype" : "test",
"parameters" : [
{
"oName" : "Param1",
"name" : "Param1",
"pid" : "011",
"type" : "582",
"description" : "testing",
"value" : "",
"version" : 1.0
},
{
"oName" : "Param2",
"name" : "Param2",
"pid" : "012",
"type" : "58222",
"description" : "testing",
"value" : "",
"version" : 1.0
}
]
},
{
"_id" : "102",
"oName" : "test op 909090",
"sid" : "05",
"name" : "test op 90909",
"oid" : "1009",
"description" : "testing",
"returntype" : "test",
"parameters" : [
{
"oName" : "Param1",
"name" : "Param1",
"pid" : "011",
"type" : "582",
"description" : "testing",
"value" : "",
"version" : 1.0
},
{
"oName" : "Param2",
"name" : "Param2",
"pid" : "012",
"type" : "58222",
"description" : "testing",
"value" : "",
"version" : 2.0
}
]
}
]
}
My update query is as follows :
db.foo.update(
{ $and : [{'_id':'04'},
{'operations._id':'100' },
{'operations.parameters.pid': '012'}]},
{
"$set": {
"operations.1.parameters.$.dummy": "foo"
}
}
)
I am using mongodb 3.6.2 referred to https://docs.mongodb.com/master/reference/operator/update/positional-filtered/
Sample record from this link :
{
"_id" : 1.0,
"grades" : [
{
"type" : "quiz",
"questions" : [
10.0,
8.0,
5.0
]
},
{
"type" : "quiz",
"questions" : [
8.0,
9.0,
6.0
]
},
{
"type" : "hw",
"questions" : [
5.0,
4.0,
3.0
]
},
{
"type" : "exam",
"questions" : [
25.0,
10.0,
23.0,
0.0
]
}
]
}
Example from this link
db.student3.update(
{},
{ $inc: { "grades.$[t].questions.$[score]": 2 } },
{ arrayFilters: [ { "t.type": "quiz" } , { "score": { $gte: 8 } } ], multi: true}
)
ERror I got from robo-3t :
cannot use the part (grades of grades.$[t].questions.$[score]) to traverse the element ({grades: [ { type: "quiz", questions: [ 10.0, 8.0, 5.0 ] }, { type: "quiz", questions: [ 8.0, 9.0, 6.0 ] }, { type: "hw", questions: [ 5.0, 4.0, 3.0 ] }, { type: "exam", questions: [ 25.0, 10.0, 23.0, 0.0 ] } ]})
Please help;
Regards
Kris
In your update operation
"$set": {
"operations.1.parameters.$.dummy": "foo"
}
refers to the 1st element in operations that is an element with "_id" : "100", and within parameters array, the $ updates the first element in array.
You need to consider using mongodb 3.6 if you want to update nested array elements using $[] to update all matching elements.
One possible way to do this in 3.4 version is fetching the required sub-document and do matches and updates on the application side.

Spring Java Mongodb Aggregation check userId in array

I have a MongoDB database with users and questions.
My Question document:
{
"_id" : "b943d57a-f4c3-4394-86f7-dd1d1b5e2563",
"_class" : "org.company.app.model.Question",
"statement" : "First question for testing purposes",
"userMail" : "themail#mail.com",
"responses" : [
{
"responseId" : "6b60e900-0fec-47d2-8853-e1ea3508abe6",
"responseType" : "TEXT",
"mail" : "themail2#mail.com",
"responseTime" : ISODate("2017-11-22T11:23:10.848Z"),
"data" : "New Response 1",
"likes" : [
"themail3#mail.com",
"themail5#mail.com"
],
"dislikes" : [],
"state" : "PUBLISHED",
"stateTime" : ISODate("2017-11-22T11:23:10.848Z")
}
],
"categories" : [
"Category1",
"Category2",
"Category3"
],
"creationTimestamp" : ISODate("2017-10-26T14:50:12.717Z"),
"lastUpdateTimestamp" : ISODate("2017-11-02T15:35:20.818Z"),
"deleted" : false,
"active" : true,
"state" : "PUBLISHED"
}
and the user document:
{
"_id" : "1f4b5091-c755-4083-880e-e1c4696f7236",
"_class" : "org.company.app.model.User",
"allowsComms" : true,
"registrationConfirmed" : false,
"placeOfWork" : "Urban records",
"name" : "John",
"surname" : "Doe",
"mail" : "themail3#mail.com",
"phone" : "[ \"1111111111\" , \"2222222222222\"]",
"birthDate" : ISODate("1971-12-10T23:00:00.000Z"),
"gender" : "MALE",
"nationality" : "US",
"language" : "en",
"socialNetworks" : {
"INSTAGRAM" : "#instagram",
"TWITTER" : "#twitter"
},
"specialty" : "alternative rock",
"subspecialty" : "sub_3",
"creationTimestamp" : ISODate("2017-10-31T10:13:12.131Z"),
"lastUpdateTimestamp" : ISODate("2017-11-13T10:23:37.637Z"),
"deleted" : false,
"active" : true,
"profileImage" : "image"
}
From that point, I'm creating a new aggregation to join both collections, get user information and count likes and dislikes instead of return all the user ids at that array.
public List<JoinedResponse> getResponses(String userId, String questionId, long itemsPerPage, long requestedPage) {
Assert.hasLength(questionId, "questionId cannot be null or empty");
MatchOperation questionIdMatch = Aggregation.match(new Criteria("_id").is(questionId));
MatchOperation responsesStateMatch = Aggregation.match(Criteria.where("responses.state")
.in(QuestionState.PUBLISHED.toString(), QuestionState.APPROVED.toString()));
LookupOperation userInfoLookUpOperation = LookupOperation.newLookup().from("User").localField("$responses.mail").foreignField("mail")
.as("userInfo");
ProjectionOperation fieldsProjectionOperation = Aggregation.project(Fields.from(
Fields.field("responseId", "$responses.responseId"),
Fields.field("responseType", "$responses.responseType"),
Fields.field("mail", "$responses.mail"),
Fields.field("responseTime", "$responses.responseTime"),
Fields.field("data", "$responses.data"),
Fields.field("state", "$responses.state"),
Fields.field("userName", "$userInfo.name"),
Fields.field("userSurname", "$userInfo.surname"),
Fields.field("profileImage", "$userInfo.profileImage")))
.and("$responses.likes").size().as("likes")
.and("$responses.dislikes").size().as("dislikes");
// Creates the Aggregation: THE ORDER IS IMPORTANT!!!
Aggregation ag = Aggregation.newAggregation(
questionIdMatch,
Aggregation.unwind("responses"),
responsesStateMatch,
userInfoLookUpOperation,
Aggregation.unwind("userInfo"),
fieldsProjectionOperation,
Aggregation.project("responseId", "userName", "userSurname", "mail", "responseType", "responseTime",
"data", "likes", "dislikes", "profileImage", "state"/*, "isLiked"*/),
Aggregation.skip((long) ((requestedPage - 1) * itemsPerPage)),
Aggregation.limit(itemsPerPage));
logger.debug(ag.toString());
AggregationResults< JoinedResponse > output = mongoTemplate.aggregate(ag, "Question",
JoinedResponse);
Well, at this point, I need to know if the userId is in the liked or disliked list and I have spend a lot of hours investigating with no result. Of course, I would like to do it with aggregation.
Any help will be appreciated.
Thank you!

How to use MongoDB $let with Spring-Data?

I have a MongoDB collection of places. A typical place has most of the following fields:
{
"_id" : ObjectId("575014dc6b028f07bef53681"),
"_class" : "domain.model.PlaceV1",
"name" : "Γιασεμί",
"primary_photo_url" : "https://irs0.4sqi.net/img/general/original/34666238_STHSh6CHiC7hpAuB4rztRVg6cFc5ylfi15aRaR7zUuQ.jpg",
"seenDetails" : NumberLong(0),
"foursquare_checkins" : 646,
"foursquare_tips" : 28,
"keywords" : [
""
],
"verified" : 1,
"location" : {
"loc" : {
"type" : "Point",
"coordinates" : [
25.898318,
36.831486
]
},
"formattedAddress" : "Χώρα",
"locality" : "Amorgos",
"first_neighbourhood" : "Katapola",
"greek_locality" : "Αμοργός",
"greek_first_neighbourhood" : "Κατάπολα"
},
"contact" : {
"phone_numbers" : [
"+30 2285 074017"
]
},
"price" : {
"priceVotes" : NumberLong(0),
"price" : 0,
"priceVotesSum" : NumberLong(0)
},
"rating" : {
"rating" : 8,
"ratingVotes" : NumberLong(0),
"ratingVotesSum" : NumberLong(0)
},
"categories" : [
{
"cat_id" : NumberLong(10310061000),
"category" : "Café",
"greek_category" : "Καφετέρια",
"weight" : 4
},
{
"cat_id" : NumberLong(11610021000),
"category" : "Bar",
"greek_category" : "Μπαρ",
"weight" : 4
}
]
}
I want to make queries where the sorting will be based on a score that is a result of some expressions and conditions. From the mongo shell I have tried this:
db.place.aggregate([
{$match:{"location.locality":"Athens"}},
{$project:
{name:1, location:1, score:{
$let: {
vars:{ foursquare: {
$cond: { if: { $gte: [ "$foursquare_checkins", 500 ] }, then: 500, else: "$foursquare_checkins" }
},
rating: {$multiply:["$rating.rating", 100]},
},
in:{$add:["$$foursquare", "$$rating", "$seenDetails"]}
}
}
}
},
{$sort: {score: -1}}]).pretty();
This is a simple example of my queries. The score will contain more complex expressions like the distance from a location. The problem is that I cannot find a way to use the $let and the $cond operator in my Java code with Spring. Could anybody help?
You should be able to do this using nested DBObject and a Custom Aggregation Operation.
For Example:
Map operations = new HashMap();
operations.put("name", 1);
operations.put("location", 1);
operations.put("score", new BasicDBObject("$let", new BasicDBObject("vars", new BasicDBObject())));
Then you can create a CustomAggregationOperation to add this to your project
CustomAggregationOperation project = new CustomAggregationOperation(new BasicDBObject("$project", operation));
This will give you the following pipeline:
{ "$project" : { "score" : { "$let" : { "vars" : { }}} , "name" : 1 , "location" : 1}}
Then you can add your other stages:
Aggregation aggregate = Aggregation.newAggregation(match, project, sort);
public class CustomAggregationOperation implements AggregationOperation {
private DBObject operation;
public CustomAggregationOperation (DBObject operation) {
this.operation = operation;
}
#Override
public DBObject toDBObject(AggregationOperationContext context) {
return context.getMappedObject(operation);
}
}

Would this situation be a mongodb server's bug?

Today I try to $set a DBRef with $db to collection.
But server say: "not okForStorage" , so operation is failed.
ps. my mongod version is 2.2
{
"ts" : ISODate("2013-01-16T14:05:42.814Z"),
"op" : "update",
"ns" : "mydb.coll1",
"query" : {
"_id" : NumberLong("928686353793211381")
},
"updateobj" : {
"$set" : {
"ref" : {
"$db" : "db2",
"$ref" : "coll2",
"$id" : NumberLong("928686353793211381")
}
}
},
"nscanned" : 1,
"keyUpdates" : 0,
"numYield" : 0,
"lockStats" : {
"timeLockedMicros" : {
"r" : NumberLong(0),
"w" : NumberLong(200)
},
"timeAcquiringMicros" : {
"r" : NumberLong(0),
"w" : NumberLong(5)
}
},
"exception" : "not okForStorage",
"exceptionCode" : 12527,
"millis" : 0,
"client" : "10.168.89.9",
"user" : ""
}
According to the docs, the order of the fields in the DBRef matters; so you'd need to reorder your ref object as:
"ref" : {
"$ref" : "coll2",
"$id" : NumberLong("928686353793211381"),
"$db" : "db2"
}

Categories

Resources