Reputation: 3641
I have a two level structure model in MongoDB, like below:
export class ProductTypeModel {
_id: ObjectID;
name: string;
children: {
_id: ObjectID,
name: string,
icon: string
}[];
}
It represents the product types in my application. Actually the children
has almost the same properties with base model, except it has an extra icon
property.
Now I have data like this:
{
"_id" : ObjectId("5b9378d9a842a7557223ebfa"),
"name" : "Clothes",
"children" : [ {
"_id" : ObjectId("5b9378d9a842a7557223ebf6"),
"name" : "Men",
"icon": "xxx"
}, {
"_id" : ObjectId("5b9378d9a842a7557223ebf7"),
"name" : "Women",
"icon": "xxx"
}, {
"_id" : ObjectId("5b9378d9a842a7557223ebf8"),
"name" : "Shoes",
"icon": "xxx"
}, {
"_id" : ObjectId("5b9378d9a842a7557223ebf9"),
"name" : "Underwear",
"icon": "xxx"
} ]
}
I want them to be selected as:
[
{ "_id" : ObjectId("5b9378d9a842a7557223ebfa"), "name" : "Clothes", "parent": null },
{ "_id" : ObjectId("5b9378d9a842a7557223ebf6"), "name" : "Men", "icon": "xxx", "parent": ObjectId("5b9378d9a842a7557223ebfa") },
{ "_id" : ObjectId("5b9378d9a842a7557223ebf7"), "name" : "Women", "icon": "xxx", "parent": ObjectId("5b9378d9a842a7557223ebfa") },
{ "_id" : ObjectId("5b9378d9a842a7557223ebf8"), "name" : "Shoes", "icon": "xxx", "parent": ObjectId("5b9378d9a842a7557223ebfa") },
{ "_id" : ObjectId("5b9378d9a842a7557223ebf9"), "name" : "Underwear", "icon": "xxx", "parent": ObjectId("5b9378d9a842a7557223ebfa") }
]
Is it possible to do this in one query in MongoDB?
I tried $unwind
but it still contains two level structure in results.
Upvotes: 1
Views: 227
Reputation: 46461
You can try below aggregation
db.collection.aggregate([
{ "$project": {
"data": {
"$map": {
"input": { "$concatArrays": ["$children", [{ "_id": "$_id", "name": "$name" }]] },
"in": {
"_id": "$$this._id",
"icon": "$$this.icon",
"name": "$$this.name",
"parent": { "$cond": [{ "$eq": ["$$this.icon", undefined] }, null, "$_id"] }
}
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" }}
])
Upvotes: 2
Reputation: 10918
All you need to do is this:
db.collection.aggregate({
$addFields: {
"children.parent": "$_id" // push the "_id" field into every array element
}
}, {
$addFields: {
"children": { $concatArrays: [ "$children", [ { "_id": "$_id", "name": "$name", "parent": null } ] ] } // a the parent item into the "children" array
}
}, {
$unwind: "$children" // flatten the array
}, {
$replaceRoot: {
"newRoot": "$children" // move all content inside the "children" field to the top
}
})
Upvotes: 0
Reputation: 2036
This will do the trick:
The parent objects and child objects are separately processed as two facets. At the end, both results are combined into one array, which is then unwinded to give everything as separate documents.
db.collection.aggregate(
{
"$unwind": {
"path": "$children"
},
},
{
"$facet": {
"parentObjs": [
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" }
}
},
{
"$addFields": {
"parent": null
}
}
],
"childObjs": [
{
"$project": {
"_id": "$children._id",
"name": "$children.name",
"icon": "$children.icon",
"parent": "$_id"
}
}
]
}
},
{
"$project": { "items": { "$concatArrays": [ "$parentObjs", "$childObjs" ] } }
},
{
"$unwind": {
"path": "$items"
}
}
)
Upvotes: 0