How can i query on embedded document using Spring data mongodb? - mongodb

My document structure looks like
{
"users":[
{
"email":"user#company.com"
"messages":[
{
"id":"1",
"text":"A",
"from":"Jessy",
"isDeleted":"false"
},{
"id":"2",
"text":"B",
"from":"Jessy",
"isDeleted":"false"
},{
"id":"3",
"text":"C",
"from":"Alan",
"isDeleted":"false"
},{
"id":"4",
"text":"D",
"from":"Amy",
"isDeleted":"true"
},{
"id":"5",
"text":"E",
"from":"Amy",
"isDeleted":"false"
}
]
}
]
}
I want to query on sub document using or & and operations.So my query can be as follow.
select all those messages from the users collection having from as "Amy" and text is "E" or some other Criteria.
I have an idea about the Aggregation Framework supported by Spring data
AggregationOperation match =Aggregation.match(Criteria.where("from").is("Amy").and("text").is("E"));
AggregationOperation unwind = Aggregation.unwind("messages");
Aggregation aggregation = Aggregation.newAggregation(match);
AggregationResults<User> result = this.mongoTemplate.aggregate(aggregation, "users", User.class);
But my problem is that i want to dynamically populate the Criteria like Criteria is at clients end he or she can pass any query to the database i gonna parse that query into Criteria. Any help will be appreciated.

Related

Upserting entity in MongoDB with adding element to nested array in one operation

I have some entities like that:
{
"id": 1,
...
"items": [
{
"name": "name_1",
...
}
]
}
I need to upsert that document in MongoDB, that means:
if there is no documents with id == 1, then save a new document
if there is document with id == 1, then I need to add 'item' from given document to persisted.
It is neccessary to do this by atomic operation.
I use SpringData and MongoTemplate and tried to use aggregate operations, but didn't manage to achieve expected behavior.
Maybe somenopne knows how to do it with Spring Data?
Your query will look something like this.
db.coll.updateMany(
{"id": 1},
{$push: {
"items": {"name": "d"}
}},
{upsert: true}
);
In spring boot,
Query query = Query.query(Criteria.where("id").is(1));
Update update = new Update();
update.push("items", <item object>);
UpdateResult updateResult = mongoTemplate.upsert(query, update, <class on which update has to run>.class);

How to convert an sql data retrieval into mongodb?

Imagine there are two collections in MongoDB User and History. Here I have written the SQL query of a data retrieval if those are in a relational DB. I want to prepare a similar query for this in MongoDB.
(History table contains many records for a particular User)
SELECT U.id FROM User U
WHERE EXISTS (SELECT * FROM History H
WHERE H.userId = U.id AND H.usage > 25 AND H.balance < 100)
AND U.category = 'VIP' AND U.area = 'XXX';
Based on my understanding of question i have written query using $lookup. Hope this is what you are looking for. Also adding link to MongoPlaygroud so that you can run the query.
db.user.aggregate([
{
"$lookup": {
"from": "history",
"localField": "id",
"foreignField": "userId",
"as": "history"
}
},
{
"$match": {
"category": "VIP",
"area": "XXX",
"history.usage": {
"$gt": 25
},
"history.balance": {
"$lt": 100
}
}
},
{
"$project": {
"id": 1
}
}
])
Try it here
There is no way to retrieve users in a collection based on a condition in a different collection.
But the following query filters users, and brings data to those you asked for. If this is not found, an empty array is retrieved, so we filter those out.
I'll show you a way with $graphLookup
//from users
var pipeline = [
{ $match:{category:"VIP", area:"XXX"} }, //get a subset of users
{ $graphLookup:{ //include docs from History as "myNewData"
from:"History",
as:"myNewData",
startWith:"$id", //match _id with
connectToField:"userId", //userId in History
connectFromField:"id", //this is irrelevant
maxDepth:0, //because is used for depth > 0
restrictSearchWithMatch:{usage:{$gt:25}, balance:{$lt:100}} //condition }
},
{$match:{"myNewData":{$elemMatch:{$exists:true}}}
}]
db.Users.aggregate(pipeline)
Collections have to be in the same database.
No data is permanently moved unless you use $out or other stage.
This should perform better than $lookup (only a subset of docs is brought over)
Test here (I just tweaked the example provided by #zac786)

How to update/insert into a mongodb collection with the result of aggregate query on the another collection

I have a below aggregate query and I wanted to insert/update this result into another collection.
db.coll1.aggregate([
{
$group:
{
_id: "$collId",
name: {$first:"$name"} ,
type: {$first:"$Type"} ,
startDate: {$first:"$startDate"} ,
endDate: {$first:"$endDate"}
}
}
])
I have another collection coll2, which has fields in the above query and some additional fields too.
I may already have the document created in Coll2 matching the _id:collId in the above query. If this Id matches, I want to update the document with the field values of the result and keep the values of other fields in the document.
If the Id does not exists, it should just create a new document in Coll2.
Is there a way to do it in the MongoDB query. I want to implement this in my Spring application.
We can use $merge to do that. It's supported from Mongo v4.2
db.collection.aggregate([
{
$group:{
"_id":"$collId",
"name": {
$first:"$name"
},
"type":{
$first:"$Type"
},
"startDate":{
$first:"$startDate"
},
"endDate":{
$first:"$endDate"
}
}
},
{
$merge:{
"into":"collection2",
"on":"_id",
"whenMatched":"replace",
"whenNotMatched":"insert"
}
}
])

MongoDB with Spring Data - Avoid $and operator

I want to create the following query using MongoDB api for Spring Data:
{ "someField": "someValue", otherField: "otherValue"}
The only way I found to do this is:
Criteria someFieldCriteria = Criteria.where("someField").is("someValue");
Criteria otherFieldCriteria = Criteria.where("otherField").is("otherValue");
Criteria andCriteria = new Criteria.andOperator(someFieldCriteria, otherFieldCriteria)
But this compiles to:
{ $and: [{ "someField": "someValue"}, { otherField: "otherValue"}] }
Anyone knows? Thanks!
Use and(String key).
Something like
Criteria andCriteria = Criteria.where("someField").is("someValue").and("otherField").is("otherValue");

mongodb - perform batch query

I need query data from collection a first, then according to those data, query from collection b. Such as:
For each id queried from a
query data from b where "_id" == id
In SQL, this can be done by join table a & b in a single select. But in mongodb, it needs do multi query, it seems inefficient, doesn't it? Or it can be done by just 2 queries?(one for a, another for b, rather than 1 plus n) I know NoSQL doesn't support join, but is there a way to batch execute queries in for loop into a single query?
You'll need to do it as two steps.
Look into the $in operator (reference) which allows passing an array of _ids for example. Many would suggest you do those in batches of, say, 1000 _ids.
db.myCollection.find({ _id : { $in : [ 1, 2, 3, 4] }})
It's very simple, don't make so many DB calls for each id, it is very inefficient, it is possible to execute a single query which will return all documents relevant to each of the ids in a single pass using the $in operator in MongoDB, which is synonymous to in syntax in SQL so for example if you need to find out the documents for 5 ids in a single pass then
const ids = ['id1', 'id2', 'id3', 'id4', 'id5'];
const results = db.collectionName.find({ _id : { $in : ids }})
This will get you all the relevant documents in a single pass.
This is basically a join in SQL parlance and you can do it in mongo using an aggregate query called $lookup.
For example if you had some types in collections like these:
interface IFoo {
_id: ObjectId
name: string
}
interface IBar {
_id: ObjectId
fooId: ObjectId
title: string
}
Then query with an aggregate like this:
await db.foos.aggregate([
{
$match: { _id: { $in: ids } } // query criteria here
},
{
$lookup: {
from: 'bars',
localField: '_id',
foreignField: 'fooId',
as: 'bars'
}
}
])
May produce resulting objects like this:
{
"_id": "foo0",
"name": "example foo",
"bars": [
{ _id: "bar0", "fooId": "foo0", title: "crow bar" }
]
}