Tintin
Tintin

Reputation: 567

Concaternate nested arrays in Mongo

I have the following schema in my collection:

{
    _id: ObjectId,
    foo: [
            {
                bar_id: ObjectId,
                bar_list: [
                    {...},
                    {...},
                ]
            },
            {
                bar_id: ObjectId,
                bar_list: [
                    {...},
                    {...},
                ]
            }
            ...
        ]
    ],
    relations: {
        relation_type: [
            {
                relation_id: ObjectId,
                ...
            }
        ]
        ...
    }
}

I wish to create a new document that has a similar structure (relations is not needed in the new document) as the input documents but that is an aggregation on relation_id and where the bar_list arrays of each input document has been concatenated (including duplicates).

To illustrate:

Document 1

{
    _id: 1,
    foo: [
            {
                bar_id: 77,
                bar_list: [
                    {a, b},
                    {c, d},
                ]
            },
            {
                bar_id: 88,
                bar_list: [
                    {u, v},
                    {w, x},
                    {y, z},
                ]
            }
        ]
    ],
    relations: {
        relation_type: [
            {
                relation_id: 666,
                ...
            }
        ]
        ...
    }
}

Document 2

{
    _id: 2,
    foo: [
            {
                bar_id: 77,
                bar_list: [
                    {a, b},
                    {e, f},
                    {g, h},
                ]
            },
            {
                bar_id: 88,
                bar_list: [
                    {u, v},
                ]
            }
        ]
    ],
    relations: {
        relation_type: [
            {
                relation_id: 666,
                ...
            }
        ]
        ...
    }
}

Output Document

{
    foo: [
            {
                bar_id: 77,
                bar_list: [
                    {a, b},
                    {c, d},
                    {a, b},
                    {e, f},
                    {g, h},
                ]
            },
            {
                bar_id: 88,
                bar_list: [
                    {u, v},
                    {w, x},
                    {y, z},
                    {u, v},
                ]
            }
        ]
    ],
}

The first part of filtering for a match against the relations.relation_type.relation_id is easy. But how to I concatenate the array based on bar_id and format the result as required? Here is what I have so far:

db.getCollection('example').aggregate([
    {'$match': {'relations.relation_type.relation_id': 666}},
    // Some sore of $group, $mergeObjects or $concatArrays next?
])

Any help appreciated.

Upvotes: 2

Views: 2060

Answers (1)

Valijon
Valijon

Reputation: 13103

Explanation

  1. With $unwind we get flatten foo.
  2. Now we $group by bar_id and accumulate bar_list array with key:pair values.
  3. We use $reduce operator to concatenate into single array bar_list arrays
  4. $group helps us accumulate insde foo top-level documents.
  5. (optional) $project removes _id field

db.collection.aggregate([
  {
    "$match": {
      "relations.relation_type.relation_id": 666
    }
  },
  {
    $unwind: "$foo"
  },
  {
    $group: {
      _id: "$foo.bar_id",
      bar_list: {
        $push: "$foo.bar_list"
      }
    }
  },
  {
    $project: {
      _id: 0,
      bar_id: "$_id",
      bar_list: {
        $reduce: {
          input: "$bar_list",
          initialValue: [],
          in: {
            $concatArrays: [
              "$$value",
              "$$this"
            ]
          }
        }
      }
    }
  },
  {
    $group: {
      _id: null,
      foo: {
        $push: "$$ROOT"
      }
    }
  },
  {
    $project: {
      _id: 0
    }
  }
])

MongoPlayground

Upvotes: 2

Related Questions