How to get data from mongodb with duplicated parameters? - java

I have try to create criteria that fetch from data base items.
Here is the code that fetches items from mongo db:
public List<Location> findByListOfId(List<String> locationsIds){
Query query = new Query();
query.addCriteria(Criteria.where("id").in(locationsIds));
return template.find(query, Location.class);
}
here is Location class defenition:
#Document("loaction")
#Data
public class Location {
#Id
private String id;
private long order;
private Date createdAt;
private Date updatedAt;
}
And here is the value of input(List locationsIds) in findByListOfId function:
List<String> locationsIds = {"5d4eee8047206b6d2df212bb","5d4eee8047206b6d2df212bb","5d4eee8047206b6d2df212bb"}
as you can see the input contains the same value three times.
The result that I get from findByListOfId function is a single item with id equal to 5d4eee8047206b6d2df212bb,
while I need to get the numbers of items with the same id as a number of times that exists with in variable(in my case I expect 3 fetched items with id = 5d4eee8047206b6d2df212bb ).
Any idea how this query can be created?

Not sure why you want to do it, but you can do it this way (in Mongo Query Language, you can then translate it in Java).
MongoDB Playground
db.collection.aggregate([
{
$match: {
key: {
$in: [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb"
]
}
}
},
{
"$addFields": {
"itemsArray": [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb"
]
}
},
{
"$unwind": "$itemsArray"
},
])
Using aggregation pipeline, you will add the array as a field using $addFields and then $unwind it (will give you x number of times).

I agree with others it's not something you want to do in production code, but I find the question interesting.
#Yahya's answer works with an assumption that the $match stage returns exactly 1 document.
The more generic pipeline to fetch exact number of documents regardless of how unique the key is and how many duplicates are in the query https://mongoplayground.net/p/546QnaFn4lV :
db.collection.aggregate([
{
$limit: 1
},
{
$project: {
_id: 1,
list: [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"6d4eee8047206b6d2df212bc",
"7d4eee8047206b6d2df212bd"
]
}
},
{
"$unwind": "$list"
},
{
"$lookup": {
"from": "collection",
"localField": "list",
"foreignField": "key",
"as": "match"
}
},
{
$project: {
match: {
$cond: [
{
$eq: [
"$match",
[]
]
},
[
{
_id: null,
"key": "$list"
}
],
"$match"
]
}
}
},
{
"$replaceWith": {
$first: "$match"
}
}
])
The first $project passes the list of requested ids to mongo.
The last $project stage returns "null" for requested ids that don't have a matching document.

Here is an aggregate query with required result:
Consider a collection with these documents:
{ _id: 1, a: 11 }
{ _id: 2, a: 22 }
{ _id: 3, a: 99 }
The query in mongo shell with input documents:
var INPUT_IDS = [ 1, 2, 1, 1 ]
db.collection.aggregate([
{
$match: {
_id: { $in: INPUT_IDS }
}
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: INPUT_IDS,
as: "inid",
in: {
$let: {
vars: {
matched: {
$filter: {
input: "$docs", as: "doc", cond: { $eq: [ "$$inid", "$$doc._id" ] }
}
}
},
in: { $arrayElemAt: [ "$$matched", 0 ] }
}
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceWith: "$docs"
}
])
The output:
{ "_id" : 1, "a" : 11 }
{ "_id" : 2, "a" : 22 }
{ "_id" : 1, "a" : 11 }
{ "_id" : 1, "a" : 11 }

Related

How to translate a mongodb aggregate query to Spring

I'm trying to translate this Mongo query to Spring:
db.doc.aggregate([
{
$match:{
$and :[
{"event_name":"event"},
{"event_data.partnerName": { "$nin" : ["something"]}},
{"event_time": {$gte : ISODate("2021-01-01T00:00:00Z")}},
{"event_time": {$lte : ISODate("2021-12-31T24:00:00Z")}}
]}
},
{
$project: {
partner: "$eveny.name",
month: {
$substr: ["$event_time", 0, 4]
}
}
},
{
$group: {
_id: {"partner": "$partner", "month": "$month"},
"Transactions": {$sum: 1}
}
},
{
$sort:{"_id.month":1}
}
]);
I have been using the Aggregation library with MatchOperations and etc. but I'm still having a hard time translating $project and $group.

How to get the count of element with non-empty-array-field when group in mongodb aggregate using Spring Data Mongo?

I have the following documents in one collection named as mail_test. Some of them have a tags field which is an array:
/* 1 */
{
"_id" : ObjectId("601a7c3a57c6eb4c1efb84ff"),
"email" : "aaaa#bbb.com",
"content" : "11111"
}
/* 2 */
{
"_id" : ObjectId("601a7c5057c6eb4c1efb8590"),
"email" : "aaaa#bbb.com",
"content" : "22222"
}
/* 3 */
{
"_id" : ObjectId("601a7c6d57c6eb4c1efb8675"),
"email" : "aaaa#bbb.com",
"content" : "33333",
"tags" : [
"x"
]
}
/* 4 */
{
"_id" : ObjectId("601a7c8157c6eb4c1efb86f4"),
"email" : "aaaa#bbb.com",
"content" : "4444",
"tags" : [
"yyy",
"zzz"
]
}
There are two documents with non-empty-tags, so I want the result to be 2.
I use the the following statement to aggregate and get the correct tag_count:
db.getCollection('mail_test').aggregate([{$group:{
"_id":null,
"all_count":{$sum:1},
"tag_count":{"$sum":{$cond: [ { $ne: ["$tags", undefined] }, 1, 0]}}
//if replace `undefined` with `null`, I got the tag_count as 4, that is not what I want
//I also have tried `$exists`, but it cannot be used here.
}}])
and the result is:
{
"_id" : null,
"all_count" : 4.0,
"tag_count" : 2.0
}
and I use spring data mongo in java to do this:
private void test(){
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(new Criteria()),//some condition here
Aggregation.group(Fields.fields()).sum(ConditionalOperators.when(Criteria.where("tags").ne(null)).then(1).otherwise(0)).as("tag_count")
//I need an `undefined` instead of `null`,or is there are any other solution?
);
AggregationResults<MailTestGroupResult> results = mongoTemplate.aggregate(agg, MailTest.class, MailTestGroupResult.class);
List<MailTestGroupResult> mappedResults = results.getMappedResults();
int tag_count = mappedResults.get(0).getTag_count();
System.out.println(tag_count);//get 4,wrong
}
I need an undefined instead of null but I don't know how to do this,or is there are any other solution?
You can use Aggregation operators to check if the field tags exists or not with one of the following constructs in the $group stage of your query (to calculate the tag_count value):
"tag_count":{ "$sum": { $cond: [ { $gt: [ { $size: { $ifNull: ["$tags", [] ] }}, 0 ] }, 1, 0] }}
// - OR -
"tag_count":{ "$sum": { $cond: [ $eq: [ { $type: "$tags" }, "array" ] }, 1, 0] }
Both, return the same result (as you had posted).

How to write mongo cli query in mongo-template for $in aggregation

This is how my data looks like
{
"_id" : "2011250546437843117",
"name" : "Book",
"textbook" : [
"Maths",
"Science"
],
"language" : [
"English"
],
"isRead" : true,
"isAvailable" : true
}
I have to filter documents based on textbook,and based on that isRead field should be true or false.
my mongo query is
db.user.aggregate([
{
$match: {
"isAvailable": true
}
},
{
$project: {
"textbook": 1,
"name": 1,
"isread": {
$in: [
"Maths",
"$textbook"
]
}
}
}
]);
I have tried to write this using mongo-template
Aggregation aggregation = newAggregation(match(Criteria.where("isAvailable").is(true)),
project("textbook","name"));
I dont understand how to write the $in operator in project stage.
Thankyou in advance.

MongoDB Query to match both single entry and array elements

I have a problem with MongoDB QueryBuilder.
Assume I have a number of documents, that can contain one or more users:
{
"_id": "document1",
"data": {
"user": {
"credentials": {
"name": "John",
"lastname": "Watson",
"middle": "Hemish"
}
}
}
}
{
"_id": "document2",
"data": {
"user": [
{
"credentials": {
"name": "John",
"lastname": "Nicholson",
"middle": "Joseph"
}
},
{
"credentials": {
"name": "Mary",
"lastname": "Watson",
"middle": ""
}
}
]
}
}
{
"_id": "document3",
"data": {
"user": [
{
"credentials": {
"name": "John",
"lastname": "Watson",
"middle": "Hemish"
}
},
{
"credentials": {
"name": "John",
"lastname": "Nicholson",
"middle": "Joseph"
}
},
{
"credentials": {
"name": "Mary",
"lastname": "Watson",
"middle": ""
}
}
]
}
}
What I am trying to do is the query, that will return only those documents containing John Watson as a user.
Here what I got so far:
1.
QueryBuilder qb = QueryBuilder.start("credentials.lastname").is("Watson").and("credentials.name").is("John");
DBObject query = QueryBuilder.start("data.user").elemMatch(qb.get()).get();
this query will return only document3: there is no array in document1 and no match in document2 (but I would like it to return document1 and document3)
2.
DBObject query = QueryBuilder.start("data.user.credentials.lastname").is("Watson").and("data.user.credentials.name").is("John").get();
this one will return all three documents: document1 and document3 are desired match, but the query will match as well document2, for it has Watson and John in query fields in the array, no matter that they are separate entries.
Is there any way to make a right query that will return document1 and document3 for John Watson?
I am trying to do it in Java, but any other example would be fine.
Right now I use a workaround combining results from both queries: first I get limit(100) results from the query with elementMatch(), then, if there are less than 100 results, I do the second query and filter all wrong matches. But I hope there is a better and more effective way to get those results.
I could give you at best like the following where user would be in an array as unwind value of the key data. I think a little bit more effort would lead you to the exact format as you want.
I am sharing it as I think it should serve the purpose or anyhow it should help you.
The aggregation query:
db.tuttut.aggregate([
{$unwind:"$data.user"},
{ $project: {
_id:1,
data:1,
temp: {name:"$data.user.credentials.name",
lastname:"$data.user.credentials.lastname"}
} } ,
{ $group:{
_id:"$_id" ,
data: {$addToSet: "$data"} ,
temp:{ $addToSet: "$temp" } } },
{ $match:{ temp:{name:"John",lastname:"Watson"} } } ,
{$project:{_id:1, data:1}}
]).pretty()
Returned Result:
{
"_id" : "document1",
"data" : [
{
"user" : {
"credentials" : {
"name" : "John",
"lastname" : "Watson",
"middle" : "Hemish"
}
}
}
]
}
{
"_id" : "document3",
"data" : [
{
"user" : {
"credentials" : {
"name" : "John",
"lastname" : "Watson",
"middle" : "Hemish"
}
}
},
{
"user" : {
"credentials" : {
"name" : "Mary",
"lastname" : "Watson",
"middle" : ""
}
}
},
{
"user" : {
"credentials" : {
"name" : "John",
"lastname" : "Nicholson",
"middle" : "Joseph"
}
}
}
]
}

Java & MongoDB - Check if field name exists in nested arrays

Looking at this simplified example of a record in Mongo:
{
"_id": ObjectId("573b30cb1d62485110330c35"),
"changes": [
{
"after": {
"comments": [
{
"user": {
"idRef": "test1234",
"name": "test user"
}
}
]
}
},
{
"after": {}
}
]
}
Is there a way, using Java's Mongo library, to iterate through each element in each nested array (changes -> after -> comments) and see if the user.idRef field exists?
You can use the following statement
db.collection.find({ "fieldToCheck" : { $exists : true, $ne : null } })
This will return a list of documents matching the criteria.

Categories