Mongo project child object but with fewer props - arrays

I have a simple document like this in my Collection named 'Partners'
[{
PartnerName: 'Company A',
MarketExpertese: [{Sector: {Code: 1, TotalYears: 1, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 2, TotalYears: 2, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 3, TotalYears: 3, TotalMonths: 20, TotalClients: 10}}]
}]
The result of desired projection would be:
[{
PartnerName: 'Company A',
MarketExpertese: [{SectorCode: 1, TotalYears: 1},
{SectorCode: 2, TotalYears: 2},
{SectorCode: 3, TotalYears: 3}]
}]
I tried the projection with Map but didnĀ“t work. If I would need just an array of SectorCode (simple array, ex. {[1, 2, 3]} ) I could do, but as I need a prop with 2 values (sectorCode and TotalYears) my map fails...
The closet I could get was this Mongo Playground

we need to use $unwind to get a stream of documents regarding the array MarketExpertese, each document will have an element from that array
then use the $project stage to format the output as we need
then use $group to group the documents we have unwind in first step
the query may look something like this
db.collection.aggregate([
{
"$unwind": "$MarketExpertese" // First we need to use unwind to get a stream of documents regarding this array, each document will have only one element from that array
},
{
"$project": {
PartnerName: 1,
Taste: {
SectorCode: "$MarketExpertese.Sector.Code",
TotalYears: "$MarketExpertese.Sector.TotalYears"
}
}
},
{
"$group": { // then group the results
"_id": "$_id",
PartenerName: {
"$first": "$PartnerName"
},
Taste: {
"$addToSet": "$Taste"
}
}
}
])
and here is a working example on Mongo Playground
Update
you can use $map to do the same functionality
db.collection.aggregate([
{
$project: {
"_id": 0,
"PartnerName": 1,
"Teste": {
"$map": {
"input": "$MarketExpertese",
"as": "elem",
"in": {
SectorCode: "$$elem.Sector.Code",
TotalYears: "$$elem.Sector.TotalYears",
}
}
}
}
}
])
and you can test it here Mongo Playground 2

Related

How to use $getfield to get a field from ROOT Document with condition in Aggregation Mongodb

I'm starting to learn Aggregate in MongoDB. I have a simple Doc as below, which has 2 fields, name and examScores, examScores is an array contains multiplier documents:
{ _id: ObjectId("633199db009be219a43ae426"),
name: 'Max',
examScores:
[ { difficulty: 4, score: 57.9 },
{ difficulty: 6, score: 62.1 },
{ difficulty: 3, score: 88.5 } ] }
{ _id: ObjectId("633199db009be219a43ae427"),
name: 'Manu',
examScores:
[ { difficulty: 7, score: 52.1 },
{ difficulty: 2, score: 74.3 },
{ difficulty: 5, score: 53.1 } ] }
Now I query the maximum score of each person using $unwind and $group/$max as below:
db.test.aggregate([
{$unwind: "$examScores"},
{$group: {_id: {name: "$name"}, maxScore: {$max: "$examScores.score"}}}
])
{ _id: { name: 'Max' }, maxScore: 88.5 }
{ _id: { name: 'Manu' }, maxScore: 74.3 }
But I want the result also contains the examScores.difficulty field corresponding to name and examScores.score, like below:
{ _id: { name: 'Max' }, difficulty: 3, maxScore: 88.5 }
{ _id: { name: 'Manu' }, difficulty: 2, maxScore: 74.3 }
I know that I can use $sort + $group and $first to achieve this goal. But I want to use $getField or any other methods to get data from ROOT Doc.
My idea is use $project and $getField to get the difficulty field from ROOT doc (or $unwind version of ROOT doc) with the condition like ROOT.name = Aggregate.name and Root.examScores.score = Aggregate.maxScore.
It will look something like this:
{$project:
{name: 1,
maxScore: 1,
difficulty:
{$getField: {
field: "$examScores.difficulty"
input: "$$ROOT.$unwind() with condition/filter"}
}
}
}
I wonder if this is possible in MongoDB?
Solution 1
$unwind
$group - Group by name. You need $push to add the $$ROOT document into data array.
$project - Set the difficulty field by getting the value of examScores.difficulty from the first item of the filtered data array by matching the examScores.score with maxScore.
db.collection.aggregate([
{
$unwind: "$examScores"
},
{
$group: {
_id: {
name: "$name"
},
maxScore: {
$max: "$examScores.score"
},
data: {
$push: "$$ROOT"
}
}
},
{
$project: {
_id: 0,
name: "$_id.name",
maxScore: 1,
difficulty: {
$getField: {
field: "difficulty",
input: {
$getField: {
field: "examScores",
input: {
$first: {
$filter: {
input: "$data",
cond: {
$eq: [
"$$this.examScores.score",
"$maxScore"
]
}
}
}
}
}
}
}
}
}
}
])
Demo Solution 1 # Mongo Playground
Solution 2: $rank
$unwind
$rank - Ranking by partition name and sort examScores.score descending.
$match - Filter the document with { rank: 1 }.
$unset - Remove rank field.
db.collection.aggregate([
{
$unwind: "$examScores"
},
{
$setWindowFields: {
partitionBy: "$name",
sortBy: {
"examScores.score": -1
},
output: {
rank: {
$rank: {}
}
}
}
},
{
$match: {
rank: 1
}
},
{
$unset: "rank"
}
])
Demo Solution 2 # Mongo Playground
Opinion: I would say this approach:
$sort by examScores.score descending
$group by name, take the first document
would be much easier.
There's no need to $unwind and then rebuild the documents again via $group to achieve your desired results. I'd recommend avoiding that altogether.
Instead, consider processing the arrays inline using array expression operators. Depending on the version and exact results you are looking for, here are two starting points that may be worth considering. In particular the $maxN operator and the $sortArray operator may be of interest for this particular question.
You can get a sense for what these two operators do by running an $addFields aggregation to see their output, playground here.
With those as a starting point, it's really up to you to make the pipeline output the desired result. Here is one such example that matches the output you described in the question pretty well (playground):
db.collection.aggregate([
{
"$addFields": {
"relevantEntry": {
$first: {
$sortArray: {
input: "$examScores",
sortBy: {
"score": -1
}
}
}
}
},
},
{
"$project": {
_id: 0,
name: 1,
difficulty: "$relevantEntry.difficulty",
maxScore: "$relevantEntry.score"
}
}
])
Which yields:
[
{
"difficulty": 3,
"maxScore": 88.5,
"name": "Max"
},
{
"difficulty": 2,
"maxScore": 74.3,
"name": "Manu"
}
]
Also worth noting that this particular approach doesn't do anything special if there are duplicates. You could look into using $filter if something more was needed in that regard.

MongoDB merge int arrays in one result

I have this aggregation result
[
{
"ids": [1,100]
},
{
"ids": [200, 100, 3]
}
]
I want to merge them as one result like this:
[
1,
200,
100,
3
]
I have tried group but the result not like I want:
{
$group: {
_id: 1,
merged: {
$push: "$ids"
}
}
}
result:
[
{
"_id": 1,
"merged": [
[1, 100],
[200, 100, 3]
]
}
]
Query
we dont have $concat accumulator
you can do it with $unwind , $group $addToSet
(null or 1 its the same, as long each member is grouped with same key, null is more common)
or you could do it with $push and $reduce $union
here is the first approach looks easier
PlayMongo
aggregate(
[{"$unwind": {"path": "$ids"}},
{"$group": {"_id": null, "ids": {"$addToSet": "$ids"}}},
{"$unset": ["_id"]}])

MongoDB query with constraint on sum of attributes

I'd like a MongoDB query that returns records where the sum of certain attributes satisfies a constraint, for instance given the following documents:
[
{ _id: 1, q1a: 20, q1b: 50, q1c: 30},
{ _id: 2, q1a: 50, q1b: 30, q1c: 20},
{ _id: 3, q1a: 0, q1b: 0, q1c: 0},
]
Id like to run a query that return all and only docs where (q1a + q1b + q1c) == 100, which in this example is records 1 and 2 above.
Is there a way to express this in MongoDB without using $where and writing the sum as a Javascript function?
You can use $expr along with $sum:
db.collection.find({ $expr: { $eq: [ 100, { $sum: [ "$q1a", "$q1b", "$q1c" ] } ] } })
Mongo Playground

Mongodb update nested array by key-value

How can I update nested array by list key value?
{
"_id": "mainId",
"events": [{
"id": 1,
"profile": 10,
} {
"id": 2,
"profile": 10,
} {
"id": 3,
"profile": 20,
}
]
}
and I have a list to update:
var list = {id: 2, newval: 222}, {id: 3, newval: 333}
How can I do an update in one query? Or in MongoDB, it will be like a loop?
for({id, val} in list){
update({_id: "mainId", events.$.id: id}, {setField: {events.$.profile: val}})
}
If you have a copy of the events array, you could make the necessary updates in your code and then send the updated array to MongoDB in a single query. Something like this
db.Test.updateOne({_id: "mainId"}, {$set: { "events": [{id: 1, profile: 222}, {id: 2, profile: 10}, {id: 3, profile: 333}] } } )
If you don't have a copy of the events array, you could do a bulk operation. Something like
db.Test.bulkWrite(
[
{ updateOne : {
"filter": {_id: "mainId", "events.id": 1},
"update": { $set: { "events.$.profile": 222 } }
}
},
{ updateOne : {
"filter": {_id: "mainId", "events.id": 3},
"update": { $set: { "events.$.profile": 333 }}
}
}
]
)
For more on bulkWrite, see the MongoDB docs: https://docs.mongodb.com/manual/core/bulk-write-operations/#bulkwrite-methods

How to get the intersection of two arrays in MongoDB ($setIntersection does not work well)

I have a simple data structure that defines people and their friends.
{
id: 0,
name: 'a',
friends: [1,2,3]
}
I need to find the common friends of two people. I managed to use the aggregation pipleline get the friends array into an array.
{ "_id" : 0, "friends" : [ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], [ 0, 1, 2 ] ] }
So the friends field is a nested array, and I want to get the intersection of its elements.
I tried to use the $setIntersection operation, however I found it does not accept a variable of an array, it only accepts an array of variables. So I have to use something like this to get the result.
{
$project: {
commonFriendIds: {
$setIntersection: [
{ $arrayElemAt: ["$friends", 0] },
{ $arrayElemAt: ["$friends", 1] }
]
}
}
}
It looks ugly, and I have to modify the code to if I need to get the common friends of 3 or more people.
Is there's a better way to accomplish this?
You can use $reduce aggregation operator. Now It will provide you the intersection for all the nested arrays inside the friends array.
db.collection.aggregate([
{ "$project": {
"commonFriendIds": {
"$reduce": {
"input": "$friends",
"initialValue": { "$arrayElemAt": ["$friends", 0] },
"in": { "$setIntersection": ["$$this", "$$value"] }
}
}
}}
])
MongoPlayground

Resources