I am new to MongoDB and I'm struggling in extract a subdocument from a document. In my case, I have the following schema:
[
{
user_id: "u1",
subscriptions: [
{
"tier": "Basic",
"requests": [
{
"name": "aaaa"
},
{
"name": "bbbb"
}
]
},
{
"tier": "Premium",
"requests": [
{
"name": "cccc"
},
{
"name": "dddd"
}
]
}
]
},
{
user_id: "u2",
subscriptions: [
{
"tier": "Premium",
"requests": [
{
"name": "eeee"
},
{
"name": "ffff"
}
]
}
]
}
]
In my case I would like to get ONLY the list of requests for user 1. This is what I would like to get :
[
{ "name": "aaaa" },
{ "name": "bbbb" },
{ "name": "cccc" },
{ "name": "dddd" }
]
How can I fix it ?
CodePudding user response:
Use $unwind
and then $project
db.collection.aggregate([
{
$match: {
user_id: "u1"
}
},
{
$unwind: "$subscriptions"
},
{
$unwind: "$subscriptions.requests"
},
{
$project: {
name: "$subscriptions.requests.name"
}
},
{
$unset: "_id"
}
])
CodePudding user response:
A simple way will be:
db.collection.aggregate([
{
$match: {
user_id: "u1"
}
},
{
$unwind: "$subscriptions"
},
{
$unwind: "$subscriptions.requests"
},
{
$project: {
"name": "$subscriptions.requests.name",
_id: 0
}
}
])
As you can see on the playground
The $unwind
separates the array elements into single documents. The $project
allows to format the document
CodePudding user response:
With the aggregation framework, you can create a pipeline that first filters out documents in the collection with the given condition using the following
a $match
pipeline
{ $match: { user_id: 'u1' } }
The next pipeline will need to display the desired output using $project
pipeline stage and the expression to achieve this will use a $reduce array operator.
This operator enables you to flatten the 2d array expression '$subscriptions.requests'
which mongo resolves from this
[
[
{ "name": "aaaa" },
{ "name": "bbbb" }
],
[
{ "name": "cccc" },
{ "name": "dddd" }
]
]
by concatenating the inner arrays with [$concatArrays
] operator 3 to just
[
{ "name": "aaaa" },
{ "name": "bbbb" },
{ "name": "cccc" },
{ "name": "dddd" }
]
without the need to introduce a couple of $unwind
steps which can potentially be a huge performance cost when you have large arrays because it produces a copy of each document per array entry, which uses more memory (possible memory cap on aggregation pipelines of 10% total memory) and thus also takes time to produce/process and that's not really necessary.
The $reduce
expression in this case becomes
{
$reduce: {
input: '$subscriptions.requests',
initialValue: [],
in: { $concatArrays: ['$$value', '$$this'] }
}
}
Thus your overall aggregation operation follows:
db.collection.aggregate([
{ $match: { user_id: 'u1' } },
{ $project: {
_id: 0,
requests: {
$reduce: {
input: '$subscriptions.requests',
initialValue: [],
in: {$concatArrays: ['$$value', '$$this']}
}
}
} }
])
CodePudding user response:
db.collection.aggregate([
{
$unwind: {path:"$subscriptions"}
},
{
$addFields :
{
"request": "$subscriptions.requests"
}
},
{
$unwind : "$request"
},
{
$addFields :
{
"name": "$request.name"
}
},
{
$project :
{
_id : 0,
name: 1
}
}
])
.toArray((err,doc)=>
{
if(!err)
{
console.log(doc)
}
})
Result will be
[
{
"name" : "aaaa"
},
{
"name" : "bbbb"
},
{
"name" : "ddd"
},
{
"name" : "eee"
},
{
"name" : "fff"
},
{
"name" : "ggg"
}
]