I need to update the dateP in the following structure with "2022-01-02", but it seems not an easy task:
{
"_id": ObjectId("5c05984246a0201286d4b57a"),
"_a": [
{
"_p": {
"s": {
"a": {}
}
}
},
{
"_onlineStore": {}
},
{
"_p": {
"s": {
"a": {
"t": [
{
c: 4
},
{
"dateP": "20200-09-20",
"l": "English",
"size": "XXL"
},
{
c: 1
}
]
},
c: {
t: 2
}
}
}
}
]
}
playground
I attempted with arrayFilters, but without success as not all elements exist in all documents and some documents are pretty empty. Please advice.
MongoDB 4.2 community
Believe that you need the filtered positional operator for _a array to check whether the document has the _p field or not.
db.collection.update({},
{
$set: {
"_a.$[a]._p.s.a.t.$[x].dateP": "2022-01-02"
}
},
{
arrayFilters: [
{
"a._p": {
$exists: true
}
},
{
"x.dateP": "20200-09-20"
}
]
})
Demo # Mongo Playground
Related
I am attempting to prepare aggregation query for faster deep nested elements count , collection is pretty big(100M docs / 1TB / mongodb 4.4) so any $unwind's make the task very slow , please, advice if there is any option to use $reduce / $filter or other faster option:
Example document:
{
"_id": ObjectId("5c05984246a0201286d4b57a"),
f: "x",
"_a": [
{
"_onlineStore": {}
},
{
"_p": [
{
"pid": 1,
"s": {
"a": {
"t": [
{
id: 1,
"dateP": "20200-09-20",
lang: "EN"
},
{
id: 2,
"dateP": "20200-09-20",
lang: "En"
}
]
},
"c": {
"t": [
{
id: 3,
lang: "en"
},
{
id: 4,
lang: "En"
},
{
id: 5,
"dateP": "20300-09-23"
}
]
}
},
h: "Some data"
}
]
}
]
}
I need to count number of "_a[]._p[]._s.c.t[]" array elements where lang: $in:["En","en" ,"EN","En","eN"]
Note elements under "_a._p._s.a.t" or "_a._p._s.d.t" shall not be included in the count ...
Expected result 1:
{ count:2}
Expected result 2:
{
id: 3,
lang: "en"
},
{
id: 4,
lang: "En"
}
Please, advice?
Thanks
1.Extended example that need to be fixed playground (count expected to be 8)
Here is my unwind version , but for big collection it looks pretty expensive:
2. Playground unwind version ( expensive )
db.myCollection.aggregate([
{
$project: {
count: {
$size: {
$filter: {
input: "$_a._p.s.t",
as: "t",
cond: { $ne: ["$$t", null] }
}
}
}
}
}
])
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
I have collection that hold 2 subdocuments fsgeneral and balancesheet. In order to compute the Book Value for different fye, I will have take the value balancesheet.shareholderFund / fsgeneral.dilutedShares.
The question is it possible to use aggregate to do this and how can this be achieved?
My input collection as below:
{
"counter": "APPLE",
"fsgeneral": {
"0": {
"fye": "Mar-01",
"dilutedShares": {
"value": 10
}
},
"1": {
"fye": "Mar-02",
"dilutedShares": {
"value": 10
}
}
},
"balancesheet": {
"0": {
"fye": "Mar-01",
"shareholderFund": {
"value": 200
}
},
"1": {
"fye": "Mar-02",
"shareholderFund": {
"value": 400
}
}
}
Expected result:
{
"counter": "APPLE",
"output": {
"0": {
"fye": "Mar-01",
"bookvalue": {
"value": 20
}
},
"1": {
"fye": "Mar-02",
"bookvalue": {
"value": 40
}
}
}
}
I have tried a few aggregates but failed to come to how 2 subdocuments can be used at the same time.
You can try,
$objectToArray convert object to array and input in $map, $map will iterate loop,
$reduce to iterate loop of fsgeneral and check key matches with balancesheet then divide using $divide
$arrayToObject, $map will return array and this will convert to again object
db.collection.aggregate([
{
$project: {
counter: 1
output: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$balancesheet" },
as: "a",
in: {
k: "$$a.k",
v: {
fye: "$$a.v.fye",
bookvalue: {
value: {
$reduce: {
input: { $objectToArray: "$fsgeneral" },
initialValue: 0,
in: {
$cond: [
{ $eq: ["$$this.k", "$$a.k"] },
{ $divide: ["$$a.v.shareholderFund.value", "$$this.v.dilutedShares.value"] },
"$$value"
]
}
}
}
}
}
}
}
}
}
}
}
])
Playground
I need to filter all documents by using multiple conditions.
{
"name": "Maths",
"excludeUserIds": [
{
"_id": 33,
"groupIds": [
"a",
"b",
"c"
]
}
]
},
{
"name": "Science",
"excludeUserIds": [
{
"_id": 24,
"groupIds": [
"a",
"x",
"b"
]
}
]
},
{
"name": "Health",
"excludeUserIds": []
},
{
"name": "English",
"excludeUserIds": [
{
"_id": 33,
"groupIds": []
}
]
}
The documents need to be returned if excludeUserIds._id not equal to given value AND excludeUserIds.groupIds are not in given array.
The mongo script I've written is
{
"$match": {
"excludeUserIds": {
"$elemMatch": {
"_id": {
$ne: 33
},
"groupIds": {
$nin: [
"a"
]
}
}
}
}
}
I expect this should return all document without Maths. But i doesn't work as I expected. Obviously I missed something. Thanks in advance. I created a Mongo playground
If the requirement is to return all subjects except Maths, your logic should be
if excludeUserIds._id not equal to given value AND OR excludeUserIds.groupIds are not in given array.
Which can be translated to the following code, which convert the logic from !A || !B to the equivalent !(A && B) for shorter code.
db.collection.aggregate([
{
$match: {
excludeUserIds: {
$not: {
$elemMatch: {
_id: 33,
groupIds: { $all: ["a"] }
}
}
}
}
}
])
Playground
If you only need to match excludeUserIds.groupIds against one value, instead of an array, you can do the following
db.collection.aggregate([
{
$match: {
excludeUserIds: {
$not: {
$elemMatch: {
_id: 33,
groupIds: "a"
}
}
}
}
}
])
Playground
Try using this
db.collection.find({
"excludeUserIds._id": { $ne: 33 },
"excludeUserIds.groupIds": { $nin: ["a"] }
})
This is probably not the solution but will give required result.
db.collection.aggregate([
{
"$match": {
"excludeUserIds": {
"$elemMatch": {
"_id": {
$ne: 33
},
"groupIds": {
"$elemMatch": {
$nin: [
"a"
]
}
}
}
}
}
}
])
Play
You need to use $elemMatch for nested array as well.
EDIT:
Updated Play
db.collection.aggregate([
{
"$match": {
"$or": [
{
"excludeUserIds": {
"$elemMatch": {
"_id": {
$ne: 33
},
"groupIds": {
"$elemMatch": {
$nin: [
"a"
]
}
}
}
}
},
{
"$or": [
{
excludeUserIds: {
$exists: false
}
},
{
excludeUserIds: null
},
{
excludeUserIds: {
$size: 0
}
}
]
},
{
"$or": [
{
"excludeUserIds.groupIds": {
$exists: false
}
},
{
"excludeUserIds.groupIds": null
},
{
"excludeUserIds.groupIds": {
$size: 0
}
}
]
}
]
}
}
])
You can check for existence/empty to get those as well.
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
});