Add field to $map entry in MongoDB - mongodb

I have MongoDB collection items with following document:
{
"values": [
{ "number1": 5, "number2": 6, "anotherProp": "...", "anotherProp2": "..." },
{ "number1": 8, "number2": 1, "anotherProp": "...", "anotherProp2": "..." }
]
}
Is there any way to add sum property to each item of values (sum = number1 + number2)? I would like to avoid naming all other properties (number1, number2, anotherProp, anotherProp2, ...), only add new one (sum). My current solution is:
db.items.aggregate([{
$project: {
values: {
$map: {
input: "$values",
as: "v",
in: {
sum: {$add: ["$$v.number1", "$$v.number2"]},
number1: "$$v.number1", // This and next 3 lines I would like to omit.
number2: "$$v.number2",
anotherProp: "$$v.anotherProp",
anotherProp2: "$$v.anotherProp2"
}
}
}
}
}])
Desired result is:
{
"values": [
{ "number1": 5, "number2": 6, "anotherProp": "...", "anotherProp2": "...", "sum": 11 },
{ "number1": 8, "number2": 1, "anotherProp": "...", "anotherProp2": "...", "sum": 9 }
]
}
Is there any way to do this? I tried use $addFields instead of $project, however result is same.

Yes, you can use $mergeObjects
db.collection.aggregate([
{
$project: {
values: {
$map: {
input: "$values",
as: "v",
in: {
"$mergeObjects": [
{
sum: {
$add: [
"$$v.number1",
"$$v.number2"
]
}
},
"$$v"
]
}
}
}
}
}
])
MongoPlayground

Related

mongoDB count array elements in deep nested array objects

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] }
}
}
}
}
}
])

MongoDB aggregation to change the key based on value in each object

Is there an aggregation query available to convert the following:
after unwind I plan to change the key based on the value inside each object and append it too has---(type)
{
"rows": [
{
"type": "aaa",
"values": [
1,
2,
3
]
},
{
"type": "bbb",
"values": [
4,
5,
6
]
}
]
}
to
{
"hasaaa": {
"type": "aaa",
"values": [
1,
2,
3
]
},
"hasbbb": {
"type": "bbb",
"values": [
4,
5,
6
]
}
}
$map to iterate loop of rows array
$concat to prepare the custom key string
return key-value from map
$arrayToObject convert key-value array to object
db.collection.aggregate([
{
$project: {
rows: {
$arrayToObject: {
$map: {
input: "$rows",
in: {
k: { $concat: ["has", "$$this.type"] },
v: "$$this"
}
}
}
}
}
}
])
Playground

Mongodb aggregation - count arrays with elements having integer value greater than

I need to write a MongoDB aggregation pipeline to count the objects having arrays containing two type of values:
>=10
>=20
This is my dataset:
[
{ values: [ 1, 2, 3] },
{ values: [12, 1, 3] },
{ values: [1, 21, 3] },
{ values: [1, 2, 29] },
{ values: [22, 9, 2] }
]
This would be the expected output
{
has10s: 4,
has20s: 3
}
Mongo's $in (aggregation) seems to be the tool for the job, except I can't get it to work.
This is my (non working) pipeline:
db.mytable.aggregate([
{
$project: {
"has10s" : {
"$in": [ { "$gte" : [10, "$$CURRENT"]}, "$values"]}
},
"has20s" : {
"$in": [ { "$gte" : [20, "$$CURRENT"]}, "$values"]}
}
},
{ $group: { ... sum ... } }
])
The output of $in seems to be always true. Can anyone help?
You can try something like this:
db.collection.aggregate([{
$project: {
_id: 0,
has10: {
$size: {
$filter: {
input: "$values",
as: "item",
cond: { $gte: [ "$$item", 10 ] }
}
}
},
has20: {
$size: {
$filter: {
input: "$values",
as: "item",
cond: { $gte: [ "$$item", 20 ] }
}
}
}
}
},
{
$group: {
_id: 1,
has10: { $sum: "$has10" },
has20: { $sum: "$has20" }
}
}
])
Using $project with $filter to get the actual elements and then via $size to get the array length.
See it working here

MongoDB: aggregating by property names

I have a collection within which each document looks a bit like this:
{
"_id" : ObjectId("5b9fe6010c969210d442a377"),
"statuses" : {
"SEARCHING" : 3
},
"regions" : {
"eu-central-1" : 1,
"us-west-2": 2
}
}
I want to group and transform this into a result set like this:
eu-central-1|us-west-2
1|2
Is this possible in one step/query?
This is probably what you want:
db.collection.aggregate({
$project: {
"regions": { $objectToArray: "$regions" } // convert sub-document into an array of key-value-pairs in order to get hold of the field names
}
}, {
$unwind: "$regions" // flatten the "regions" array
}, {
$group: {
"_id": "$regions.k",
"count": { $sum: "$regions.v" } //
}
})
Alternatively, if you really want to get a pipe-delimited output here is what you'd do:
db.collection.aggregate({
$project: {
"result": {
$reduce: {
"input": { $objectToArray: "$regions" },
"initialValue": { k: "", v: "" }, // start with empty strings for both key and value
"in": {
k: { $concat: [ "$$value.k", "|", "$$this.k" ] }, // concatenate existing value with "|" followed by currently looked at value for both key and value
v: { $concat: [ "$$value.v", "|", { $substr: [ "$$this.v", 0, 1000 ] } ] } // substr is needed to convert an integer field into a string
//v: { $concat: [ "$$value.v", "|", { $toString: "$$this.v" } ] } // this works from MongoDB v4.0 onwards and looks a bit cleaner
}
}
}
}
}, {
$project: { // remove the leading "|"
"result.k": { $substr: [ "$result.k", 1, -1 ] },
"result.v": { $substr: [ "$result.v", 1, -1 ] }
}
})

Mongodb Query for an array with more than word structure

If you have an array in mongodb as follows:
"tokens": [
{
"index": 1,
"word": "I",
"originalText": "I",
"lemma": "I",
"characterOffsetBegin": 0,
"characterOffsetEnd": 5,
"pos": "NNP",
"ner": "PERSON",
"before": "",
"after": " "
},
{
"index": 2,
"word": "played",
"originalText": "played",
"lemma": "play",
"characterOffsetBegin": 6,
"characterOffsetEnd": 11,
"pos": "VBZ",
"ner": "O",
"before": " ",
"after": " "
},
{
"index": 3,
"word": "football",
"originalText": "football",
"lemma": "football",
"characterOffsetBegin": 22,
"characterOffsetEnd": 24,
"pos": "IN",
"ner": "O",
"before": " ",
"after": " "
}
]
and I want to query this array as follows:
I need to check if the (word:I) and (word which contains word:regex(p.*) and pos:VBZ) are in this array or not? if yes I need to return that array.
$elemMatch didn't help as I search for two conditions in that array {"word":"I" and ("word":/p.* and "pos":"VBZ") together and in order
Anyone can help me in this issue?
OK, I think I get what you want, and it's a little tricky because:
If you didn't have the index field you would rely on the order of the array elements which is bad practice
The solution below is not generic and it will be hard to modify it if you want additional parameters (like more than 2 elements, more complicated regex).
What I wanted to achieve in this solution is to $filter the matched elements and check if the filtered indexes are $subtract into 1 which means they are in order:
db.test.aggregate([
{
$addFields: {
filtered_tokens: {
$filter: {
input: '$tokens',
as: 'token',
cond: {
$or: [
{
$eq: ['$$token.word', 'I']
},
{
$and: [
{
$eq: [{$substr: ['$$token.word', 0, 1]}, 'p']
},
{
$eq: ['$$token.pos', 'VBZ']
}
]
}
]
}
}
}
}
},
{
$match: {
filtered_tokens: {$size: 2}
}
},
{
$addFields: {
filtered_tokens: {
$subtract: [
{
$arrayElemAt: ['$filtered_tokens.index', 1]
},
{
$arrayElemAt: ['$filtered_tokens.index', 0]
}
]
}
}
},
{
$match: {
filtered_tokens: 1
}
}
])
I'm not sure I understood what you need, but I think this it what you are looking for:
db.test.find({
$and: [
{
'tokens.word': 'I'
},
{
tokens: {
$elemMatch: {
word: /p.*/,
pos: 'VBZ'
}
}
}
]
})