I'm struggling to filter a nested array at the projection stage.
The actual match condition is more complicated, but this will do:
db.collection.insertMany([
{ "id": 0,
"list_L1": [
{ "obj_L1": {
"list_L2": [
{id: "000", go: "Y"},
{id: "001", go: "N"},
{id: "002", go: "N"} ]}}]},
{ "id": 1,
"list_L1": [
{ "obj_L1": {
"list_L2": [
{id: "100", go: "Y"},
{id: "101", go: "Y"},
{id: "102", go: "N"} ]}}]},
{
"id": 2,
"list_L1": [
{ "obj_L1": {
"list_L2": [
{id: "200",go: "N"},
{id: "201",go: "N"},
{id: "202",go: "N"} ]}}]}
]);
db.collection.aggregate([
{$match: { "list_L1.obj_L1.list_L2.go": "Y"}}
]);
(Apologies if the format is weird - I'm trying to save vertical space)
This filters documents alright, but I've been asked to also keep only the matching elements of list_L2
, producing something like this:
[
{ "_id": ObjectId("5a934e000102030405000000"),
"id": 0,
"list_L1": [
{ "obj_L1": {
"list_L2": [
{id: "000", go: "Y"} ]}}},
{ "_id": ObjectId("5a934e000102030405000001"),
"id": 1,
"list_L1": [
{ "obj_L1": {
"list_L2": [
{id: "100", go: "Y"},
{id: "101", go: "Y"}} ]}}}
]
If the document only had list_L1
it wouldn't be hard, but I can't "see" the values two levels deep; this returns empty arrays:
db.collection.aggregate([
{$match: { "list_L1.obj_L1.list_L2.go": "Y"}},
{$addFields: { "list_L1.obj_L1.list_L2": {
$filter: {
input: "$list_L1.obj_L1.list_L2",
cond: { $eq: ["$$this.go","Y"] }}}}}
]);
[
{
"_id": ObjectId("5a934e000102030405000000"),
"list_L1": [
{ "obj_L1": {
"list_L2": [] }}]},
{
"_id": ObjectId("5a934e000102030405000001"),
"list_L1": [
{
"obj_L1": {
"list_L2": [] }}]}
]
So how do I filter the inner array after matching what I want? Alternative, Icould remove the non-matching elements of all documents and then filter for non-empty, but I'm too new to this.
Refactor your schema if possible. Store the innermost array elements as individual documents will help you gain query simplicity and potential performance boost through indexing.
Nevertheless, for your current scenario, you can use $map
to iterate through list_L1
array. Chain up $mergeObjects
and $filter
to return only matched elements in list_L2
db.collection.aggregate([
{
"$match": {
"list_L1.obj_L1.list_L2.go": "Y"
}
},
{
"$set": {
"list_L1": {
"$map": {
"input": "$list_L1",
"as": "ll1",
"in": {
"$mergeObjects": [
"$$ll1",
{
"obj_L1": {
"list_L2": {
"$filter": {
"input": "$$ll1.obj_L1.list_L2",
"as": "ll2",
"cond": {
"$eq": [
"Y",
"$$ll2.go"
]
}
}
}
}
}
]
}
}
}
}
}
])