Spring mongo: Query Subdocument array to get matching subdocuments using IN clause - mongodb

Hi have a collection as below:
clientPref
{
clntId: 1234,
clntType: "internal",
status: "PROCESSED",
prefs: [
{
name: "AAA",
value: "value1"
},
{
name: "BBB",
value: "value2"
},
{
name: "CCC",
value: "value3"
}
]
}
If I find by prefs.name $in ("AAA", "CCC"), I'm getting all the subdocuments along with the parent.
I then tried using prefs.$: 1 in the fields parameter of #Query but then it's returning the first matching subdocument only.
Desired output
{
clntId: 1234,
clntType: "internal",
status: "PROCESSED",
prefs: [
{
name: "AAA",
value: "value1"
},
{
name: "CCC",
value: "value3"
}
]
}
Is there a way I can get AAA and CCC subdocument by using #Query annotation. If not then how to do it using aggregation? Still pretty new to mongo so not able to figure out a way to get desired result.

You need to project what is needed.
{
"prefs.$":1,
"clntType":1,
"status":1
}
play
Query:
db.collection.find({
"prefs.name": "AAA"
},
{
"prefs.$": 1,
"clntType": 1,
"status": 1
})
Output:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"clntType": "internal",
"prefs": [
{
"name": "AAA",
"value": "value1"
}
],
"status": "PROCESSED"
}
]
To get all sub doc:
Play
db.collection.aggregate([
{
"$unwind": "$prefs"
},
{
"$match": {
"prefs.name": {
"$in": [
"AAA",
"CCC"
]
}
}
}
])
If you want to group all the data again, you can do play
db.collection.aggregate([
{
"$unwind": "$prefs"
},
{
"$match": {
"prefs.name": {
"$in": [
"AAA",
"CCC"
]
}
}
},
{
"$group": {
"_id": "$_id",
"data": {
"$push": "$$ROOT"
}
}
}
])

Related

MongoDB returning two array while using alias

I am a json in mongodb. The structure is below --
{
id: "1",
name: "sample",
user: [
{
data_alias: "ex",
value: "efg"
}
]
}
Now I want the the data_alias to be data after mongodb returns the result.
When I am using below query --
db.coll.find(
{"id":"1"},
{"data": "$user.data_alias","_id": 0,"value":1}
)
Now it is retuning data like --
{
"user": [
{
"value": "efg",
},
],
"data": [
"ex"
]
}
But I want the returning value should be like --
{
"data": "ex",
"name": "sample"
}
Also I have tried with aggregate function
db.colls.aggregate([
{
$match: {
"id": "1"
}
},
{
"$project": {
"_id": 0,
"data": "$user.data_alias"
}
}
]);
Both the queries returning same result.
Just $unwind the user array and $project to your expected output.
db.collection.aggregate([
{
$match: {
name: "sample"
}
},
{
"$unwind": "$user"
},
{
"$project": {
_id: 0,
"name": 1,
"data": "$user.data_alias"
}
}
])
Here is the Mongo playground for your reference.

Mongo $cond if expression doesn't work like $match

I have a collection with documents with a "parent" field.
[
{
"parent": "P1",
"tagGroups": [],
},
{
"parent": "P1",
"tagGroups": [
{
group: 1,
tags: {
tag1: {
value: true
},
tag2: {
value: "foo"
},
}
},
{
group: 2,
tags: {}
}
]
},
{
"parent": "P2",
"tagGroups": [],
}
]
I want to make request that retrieves all documents with the same parent when at least one match with my criteria: tag1.value = true.
Expected:
[
{
"parent": "P1",
"tagGroups": [],
},
{
"parent": "P1",
"tagGroups": [
{
group: 1,
tags: {
tag1: {
value: true
},
tag2: {
value: "foo"
},
}
},
{
group: 2,
tags: {}
}
]
}
]
For that I wanted to use the $cond to flag every document, then group by parent.
https://mongoplayground.net/p/WiIlVeLDrY-
But the "if" part seems to work differently that a $match
https://mongoplayground.net/p/_jcoUHE-aOu
Do you have another efficient way to do that kind of query?
Edit: I can use a lookup stage but I'm afraid of bad performances
Thanks
You haven't mentioned what you want to achieve, but you expect that your tried code (first link) should be working. You need to use $in instead of $eq in your query
db.collection.aggregate({
"$addFields": {
"match": {
"$cond": [
{ $in: [ true, "$tagGroups.tags.tag1.value" ] }, 1, 0] }
}
},
{
"$group": {
"_id": "$parent",
"elements": { "$addToSet": "$$ROOT" },
"elementsMatch": { "$sum": "$match" }
}
},
{ "$match": { "elementsMatch": { $gt: 0 } }},
{ "$unwind": "$elements"}
)
Working Mongo playground
Note : You have asked about the efficient way. Better you need to post expected result

Mongodb aggregation with $addFileds and condition

Given that:
db :
{
id:"112",
val1: {val:""},
val2: {val:"123"},
}
I would like to run a script that updates a new field according to the aggregation result. The result is true if one of the values (val1, val2) is empty
The below is what I did with aggregation and then I would go over with for and update all rows:
db.valTest.aggregate(
[{
"$addFields": {
"val.selected": {
'$or': [{
'val1.val': ''
}, {
'val2.val': ''
}]
}
}
},
{
"$group": {
"_id": "$_id",
"id": {
"$first": "$id"
},
"value": {
"$first": "val1.val"
},
"result": {
"$push": {
"val": "val1.val",
"selected": "val.selected"
}
}
}
}
]
)
But, I do not get the correct result. I would like to get result like:
{
id:"112",
val1: {val:""},
val2: {val:"123"},
result: true
},
{
id:"114",
val1: {val:"4545"},
val2: {val:"123"},
result: false
}
Presently, I am getting the following error:
"message" : "FieldPath field names may not contain '.'.",
You need to use $eq aggregation operator for the matching criteria
db.collection.aggregate([
{ "$addFields": {
"result": {
"$cond": [
{ "$or": [{ "$eq": ["$val1.val", ""] }, { "$eq": ["$val2.val", ""] }] },
true,
false
]
}
}}
])

Aggregation on complex objects

I have a collection with documents like the following:
{
"towers": [
{
"name": "foo",
"towers": [
{
"name": "A",
"buildType": "Apartament"
},
{
"name": "B",
"buildType": "Apartament"
}
]
},
{
"name": "xpto",
"towers": [
{
"name": "C",
"buildType": "House"
},
{
"name": "D",
"buildType": "Office"
}
]
}
]
}
All I need to know is what are all the possible values for "buildType", like:
Apartment
House
Office
It's a complex object and the data to aggregate is deep inside it. Is there any way to achieve the results I want?
You need to $unwind the two nested array that is "towers" and "towers.towers" and then use $group with "towers.towers.buildType" field to get the distinct values
db.collection.aggregate([
{ "$unwind": "$towers" },
{ "$unwind": "$towers.towers" },
{ "$group": {
"_id": "$towers.towers.buildType"
}}
])
Output
[
{
"_id": "Office"
},
{
"_id": "House"
},
{
"_id": "Apartament"
}
]
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: "$towers",
}
},
// Stage 2
{
$unwind: {
path: "$towers.towers",
}
},
// Stage 3
{
$group: {
_id: '$_id',
buildType: {
$addToSet: '$towers.towers.buildType'
}
}
},
]
);

Mongodb array $push and $pull

I was looking to pull some value from array and simultaneously trying to update it.
userSchema.statics.experience = function (id,xper,delet,callback) {
var update = {
$pull:{
'profile.experience' : delet
},
$push: {
'profile.experience': xper
}
};
this.findByIdAndUpdate(id,update,{ 'new': true},function(err,doc) {
if (err) {
callback(err);
} else if(doc){
callback(null,doc);
}
});
};
i was getting error like:
MongoError: exception: Cannot update 'profile.experience' and 'profile.experience' at the same time
I found this explanation:
The issue is that MongoDB doesn’t allow multiple operations on the
same property in the same update call. This means that the two
operations must happen in two individually atomic operations.
And you can read that posts:
Pull and addtoset at the same time with mongo
multiple mongo update operator in a single statement?
In case you need replace one array value to another, you can use arrayFilters for update.
(at least, present in mongo 4.2.1).
db.your_collection.update(
{ "_id": ObjectId("your_24_byte_length_id") },
{ "$set": { "profile.experience.$[elem]": "new_value" } },
{ "arrayFilters": [ { "elem": { "$eq": "old_value" } } ], "multi": true }
)
This will replace all "old_value" array elements with "new_value".
Starting from MongoDB 4.2
You can try to update the array using an aggregation pipeline.
this.updateOne(
{ _id: id },
[
{
$set: {
"profile.experience": {
$concatArrays: [
{
$filter: {
input: "$profile.experience",
cond: { $ne: ["$$this", delet] },
},
},
[xper],
],
},
},
},
]
);
Following, a mongoplayground doing the work:
https://mongoplayground.net/p/m1C1LnHc0Ge
OBS: With mongo regular update query it is not possible.
Since Mongo 4.2 findAndModify supports aggregation pipeline which will allow atomically moving elements between arrays within the same document. findAndModify also allows you to return the modified document (necessary to see which array elements were actually moved around).
The following includes examples of:
moving all elements from one array onto the end of a different array
"pop" one element of an array and "push" it to another array
To run the examples, you will need the following data:
db.test.insertMany( [
{
"_id": ObjectId("6d792d6a756963792d696441"),
"A": [ "8", "9" ],
"B": [ "7" ]
},
{
"_id": ObjectId("6d792d6a756963792d696442"),
"A": [ "1", "2", "3", "4" ],
"B": [ ]
}
]);
Example 1 - Empty array A by moving it into array B:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696441") },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, "$A" ] } } },
{ $set: { "A": [] } }
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696441"
},
"A": [],
"B": [
"7",
"8",
"9"
]
}
Example 2.a - Pop element from array A and push it onto array B
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"A": {$exists: true, $type: "array", $ne: [] }},
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ { $first: "$A" } ] ] } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696442"
},
"A": [
"2",
"3",
"4"
],
"B": [
"1"
]
}
Example 2.b - Pop element from array A and push it onto array B but in two steps with a temporary placeholder:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: false } },
update: [
{ $set: { "temp": { $first: "$A" } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
// do what you need to do with "temp"
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: true } },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ "$temp" ] ] } } },
{ $unset: "temp" }
],
new: true
});