Mongodb - Merge nested subdocuments array based on specific properties - mongodb

I've built an aggregation that is returning this data:
[
{
"users": [
{
_id: 1,
name: "John",
age: 31
},
{
_id: 2,
name: "Jane",
age: 26
}
],
"teams": [
{
id: 1,
name: "Team 1",
color: "yellow"
},
{
id: 2,
name: "Team 2",
color: "red"
}
],
"moreTeams": [
{
id: 1,
name: "Team 1 - More",
},
{
id: 2,
name: "Team 2 - More",
},
{
id: 3,
name: "Team 3 - More",
extra: "extra"
}
]
}
]
How can I group "teams" and moreTeams into the same array (based on id), keeping all properties and eventually overriding where they have the same names?
Here's my desired result:
[
{
"users": [
{
_id: 1,
name: "John",
age: 31
},
{
_id: 2,
name: "Jane",
age: 26
}
],
"groupedTeams": [
{
id: 1,
name: "Team 1 - More",
color: "yellow"
},
{
id: 2,
name: "Team 2 - More",
color: "red"
},
{
id: 3,
name: "Team 3 - More",
extra: "extra"
}
]
}
]
I've tried using $unwind and $group with no success :(
Palyground example: https://mongoplayground.net/p/yZY8oQ9r-N1

$map to iterate loop of moreTeams array
$filter to iterate loop of teams array and filter matching object by id
$arrayElemAt to get first element object from above filter
$mergeObjects to merge above object from arrayElemAt and current object
db.collection.aggregate([
{
$addFields: {
moreTeams: {
$map: {
input: "$moreTeams",
as: "m",
in: {
$mergeObjects: [
{
$arrayElemAt: [
{
$filter: {
input: "$teams",
cond: { $eq: ["$$m.id", "$$this.id"] }
}
},
0
]
},
"$$m"
]
}
}
}
}
},
Now we have merged moreTeams in teams but we have to merge teams in moreTeams as well
$project, $filter to iterate loop of teams array and match condition if id is not in moreTeams
$concatArrays to concat moreTeams and above filtered array that is not in moreTeams
{
$project: {
users: 1,
moreTeams: {
$concatArrays: [
"$moreTeams",
{
$filter: {
input: "$teams",
cond: { $not: { $in: ["$$this.id", "$moreTeams.id"] } }
}
}
]
}
}
}
])
Playground

Related

Adding parent field value in subdocuments while concating

I want to add parent field value inside its subDocs
I want task name inside all of its subdocuments. Example document:
{
_id: 1,
tasks: [
{
_id: 1,
assigned: [
{
_id: 1,
name: "assigned1",
solutions: [
{_id: 1, name: "solution 1"},
{_id: 2, name: "solution 2"}
]
},
{
_id: 2,
name: "assigned2",
solutions: [
{_id: 1, name: "solution 1"},
{_id: 2, name: "solution 2"}
]
}
]
}
]
}
Below is Necessary data related to this question
Mongo PlayGround
If I understand correctly, you want to use $map and $mergeObjects for three hierarchies:
db.collection.aggregate([
{
$set: {
tasks: {
$map: {
input: "$tasks",
as: "task",
in: {
$mergeObjects: [
"$$task",
{
assigned: {
$map: {
input: "$$task.assigned",
as: "assigned",
in: {
$mergeObjects: [
"$$assigned",
{
solutions: {
$map: {
input: "$$assigned.solutions",
as: "solution",
in: {
$mergeObjects: [
"$$solution",
{
taskName: "$$assigned.name"
}
]
}
}
}
}
]
}
}
}
}
]
}
}
}
}
}
])
See how it works on the playground example

Merge nested array data in single array and get them into root of document

I am struggling with retrieving nested document in root.
below i have a schema inside it there is a array of tasks in which objects are present,
in those objects there is again an array of assigned objects and in those objects i have solution array.
now i want to merge all solutions in one array and get that solution array in root od document.
Schema -
{
_id: 1,
tasks: [
{
_id: 1,
assigned: [
{
_id: 1,
solutions: [
{
_id: 1,
name: 'solution 1',
},
{
_id: 2,
name: 'solution 2',
},
],
},
{
_id: 2,
solutions: [
{
_id: 1,
name: 'solution 1',
},
{
_id: 2,
name: 'solution 2',
},
],
},
],
},
],
};
I want to merge all solutions to a single array based on some condition and set that array into new field in root of collection.
const order = this.orderModel
.aggregate([
{ $match: { _id: orderId, student: studentId } },
{
$addFields: {
solutions: {
$map: {
input: '$tasks.assigned.solutions',
as: 's',
in: '$$s',
},
},
},
},
])
.exec();
output i am getting -
"solutions": [
[
[
{
"id": 1,
"name": "solution 1",
}
],
[
{
"_id": 2,
"name": "solution 2"
}
]
]
],
Maybe something like this:
db.collection.aggregate([
{
"$project": {
"solutions": {
"$reduce": {
"input": "$tasks",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
{
$reduce: {
input: "$$this.assigned",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this.solutions"
]
}
}
}
]
}
}
}
}
}
])
Explained:
Use $project and two nested $reduce/$concatArrays to join the "solutions" array objects under the new array field "solutions" in the document root.
Playground
if you want to filter based on some condition you can replace "$$this.solutions" with:
{
$filter: {
input: "$$this.solutions",
cond: { $eq: ["$$this._id", 1] }
}
}
will filter only documents with _id:1
see example here

MongoDB Query: How can I aggregate an array of objects as a string

I have an array of objects where I want to make a string concatenating all of the same attributes from the array. Example:
{
_id: 123,
example_document: true,
people: [
{
name: "John",
age: 18
}, {
name: "Clint",
age: 20
}
]
}
And I wanna make a query where my result would be:
{
_id: 123,
example_document: true,
people: [
{
name: "John",
age: 18
}, {
name: "Clint",
age: 20
}
],
concat_names: "John, Clint"
}
I think aggregate is the path I should take, but I'm not being able to find a way of getting a string out of this, only concat array elements into another array. Anyone could help?
You can use $concat combined with $reduce to achieve this, like so:
db.collection.aggregate([
{
$addFields: {
concat_names: {
$reduce: {
input: "$people",
initialValue: "",
in: {
$concat: [
"$$value",
{
$cond: [
{
$eq: [
{
"$strLenCP": "$$value"
},
0
]
},
"",
", "
]
},
"$$this.name"
]
}
}
}
}
}
])
Mongo Playground
You can use aggregation operators $project, and $map:
db.collection.aggregate([
{
$project:
{
concat_names:
{
$map:
{
input: "$people",
as: "i",
in: "$$i.name"
}
}
}
}])

Sum of subdocument and document properties conditionally in MongoDB

I'm trying to get a sum of all room notifications + subroom notifications (this one's only if If there is a mutual element between subRoom Roles and Users[x].Roles) and getting aswell a list of all subRooms without taking into account if user has matching roles or not.
*Roles allow Users to access or not to SubRooms
Documents:
[
{
_id: 1,
id: 1,
room: "Room1",
notifications: [
{
id: 1,
read: []
},
{
id: 2,
read: []
},
{
id: 3,
read: ["User1"]
}
],
users: [{
userId: "User1",
roles: ["A", "D"]
},{
userId: "User2",
roles: ["B", "C", "D"]
}],
subRooms: [
{
id: "SubRoom1",
roles: ["A", "B", "C"]
notifications: [
{
id: 1,
read: []
},
{
id: 2,
read: ["User2"]
},
{
id: 3,
read: ["User1", "User2"]
}
]
},
{
id: "SubRoom2",
roles: ["C"]
notifications: [
{
id: 1,
read: []
},
{
id: 2,
read: []
},
{
id: 3,
read: ["User2"]
}
}
]
}
]
Expected Result:
For User1 Search:
[
{
_id: 1,
id: 1,
room: "Room1",
subRooms: ["SubRoom1", "SubRoom2"] // All subRooms without taking into account if user has matching roles or not
totalNotReadNotifications: 4 // 2 notifications of SubRoom1 + 2 notifications of Room
}
]
For User 2 Search:
[
{
_id: 1,
id: 1,
room: "Room1",
subRooms: ["SubRoom1", "SubRoom2"] // All subRooms without taking into account if user has matching roles or not
totalNotReadNotifications: 6 // 1 notification of SubRoom 1 + 2 notifications of SubRoom2 + 3 notifications of Room
}
]
Thank you very much in advance.
$project to show required fields
get main total unread notifications to count by $filter and $size operators
get filtered subRoom by $let and $filter operators
$addFields to get total unread notifications from filtered subRoom and sum with main notifications count
$unset to remove subRoom field its not needed
db.collection.aggregate([
{
$project: {
id: 1,
room: 1,
subRooms: "$subRoom.id",
totalNotReadNotifications: {
$size: {
$filter: {
input: "$notifications",
cond: { $not: { $in: ["User1", "$$this.read"] } }
}
}
},
subRoom: {
$let: {
vars: {
user: {
$arrayElemAt: [
{
$filter: {
input: "$users",
cond: { $eq: ["$$this.userId", "User1"] }
}
},
0
]
}
},
in: {
$filter: {
input: "$subRoom",
cond: {
$ne: [
{
$filter: {
input: "$$this.roles",
cond: { $in: ["$$this", "$$user.roles"] }
}
},
[]
]
}
}
}
}
}
}
},
{
$addFields: {
totalNotReadNotifications: {
$add: [
"$totalNotReadNotifications",
{
$sum: {
$map: {
input: "$subRoom",
in: {
$size: {
$filter: {
input: "$$this.notifications",
cond: { $not: { $in: ["User1", "$$this.read"] } }
}
}
}
}
}
}
]
}
}
},
{ $unset: "subRoom" }
])
Playground

how to retrive single element of a nested array in mongodb?

let's say i have documents like this in my mongodb:
{
id: 1,
names: [
{
id: 2,
first: [
{
id: 3,
name: "alice",
},
{
id: 4,
name: "bob",
},
]
},
{
id: 5,
first: [
{
id: 6,
name: "berhe",
},
{
id: 7,
name: "belay",
},
]
}
]
}
{
id: 8,
names: [
{
id: 9,
first: [
{
id: 10,
name: "gemechu",
},
{
id: 11,
name: "samy",
},
]
},
{
id: 12,
first: [
{
id: 13,
name: "helen",
},
{
id: 14,
name: "natu",
},
]
}
]
}
now how to retrieve a value with
id=8,
names.id = 9,
names.first.id=10
which is like this:
{
id:10,
name:"gemechu"
}
$match to filter documents by all conditions using $elemMatch,
$unwind deconstruct names array
$match names id condition
$unwind deconstruct first array
$match first id condition
$project to format result
db.collection.aggregate([
{
$match: {
id: 8,
names: {
$elemMatch: {
id: 9,
"first.id": 10
}
}
}
},
{ $unwind: "$names" },
{ $match: { "names.id": 9 } },
{ $unwind: "$names.first" },
{ $match: { "names.first.id": 10 } },
{
$project: {
id: "$names.first.id",
name: "$names.first.name"
}
}
])
Playground