Sirpingalot
Sirpingalot

Reputation: 447

Merge documents from 2 collections in MongoDB & overwrite properties on a field

I have 2 collections in MongoDB :

Collection1 :

{
    _id:1,            
    Field1: "Some info",
    Field2: "Some other info",
    Elements: [
        {
            id: 0,
            Enabled: false
        },
        {
            id: 1,
            Enabled: false
        },
        {
            id: 2,
            Enabled: false
        }
    ]
 }

Collection2 :

{
    Identifier: "identifier",
    ElementsOverride: [
        {
            id: 0,
            Enabled: true                    
        },
        {
            id: 1,
            Enabled: false
        },
        {
            id: 2,
            Enabled: true                    
        }
    ]
 }

What I would like to do is perform an operation which flattens "Element" collection and returns Collection1 with the flattened Element collection (basically the Enabled field from collection 2 overwrites the enabled field of Collection 1.

Is there a way to achieve this in Mongodb?


Adding more clarification for what the output should be like: Essentially what I'm trying to do is merge the document identified by _id:1 in collection 1 (document1), with the document identified by Identifier: "identifier" in collection 2 (document 2) such that:

Required Output :

{
    _id:1,     
    Identifier: "identifier",       
    Field1: "Some info",
    Field2: "Some other info",
    Elements: [
        {
            id: 0,
            Enabled: true
        },
        {
            id: 1,
            Enabled: false
        },
        {
            id: 2,
            Enabled: true
        }
    ]
 }

Upvotes: 1

Views: 1072

Answers (2)

prasad_
prasad_

Reputation: 14287

I am not sure how you want the output.

flattens "Element" collection

generally means the array Element is unwound. Please correct my interpretation, in case I have misunderstood.

But, the following steps in Mongo Shell will get the result:

arr1 = db.c1.aggregate( [ { $unwind: "$Elements" }, { $sort: { "Elements.id": 1 } ] ).toArray()
arr2 = db.c2.aggregate( [ { $unwind: "$ElementsOverride" }, { $sort: { "ElementsOverride.id": 1 } ] ).toArray()

for (let i=0; i < arr1.length; i++) {
    updated = Object.assign(arr1[i].Elements, arr2[i].ElementsOverride);
    arr1[i].Elements = updated
}

The variable arr1 will have:

[
        {
                "_id" : 1,
                "Field1" : "Some info",
                "Field2" : "Some other info",
                "Elements" : {
                        "id" : 0,
                        "Enabled" : true
                }
        },
        {
                "_id" : 1,
                "Field1" : "Some info",
                "Field2" : "Some other info",
                "Elements" : {
                        "id" : 1,
                        "Enabled" : false
                }
        },
        {
                "_id" : 1,
                "Field1" : "Some info",
                "Field2" : "Some other info",
                "Elements" : {
                        "id" : 2,
                        "Enabled" : true
                }
        }
]



[ EDIT ADD ]

Updated to reflect the required output:

arr2 = db.c2.aggregate( [ 
                       { $unwind: "$ElementsOverride" }, 
                       { $replaceRoot: { newRoot: "$ElementsOverride" } }
] ).toArray()

db.c1.aggregate( [ 
{ $unwind: "$Elements" }, 
{ $addFields: { 
       "Elements.Enabled": {
           $filter: {
               input: arr2,
               cond: { $eq: [ "$$this.ElementsOverride.id", "$Elements.id" ] }
           }
       }
} },
{ $group: { 
       _id: "$_id", 
       doc: { $first: "$$ROOT"}, 
       Identifier: { $first: "$Elements.Enabled.Identifier"}, 
       Elements: { $push: { $arrayElemAt: [ "$Elements.Enabled", 0 ] } } 
} },
{ $addFields: { 
       "doc.Elements": "$Elements.ElementsOverride",  
       "doc.Identifier": { $arrayElemAt: [ "$Identifier", 0 ]  }
} },
{ $replaceRoot: { newRoot: "$doc" } }
] )


[ EDIT ADD 2 ]

Here is another way of merging the documents:

doc1 = db.c1.findOne()
arr2 = db.c2.aggregate( [ { $unwind: "$ElementsOverride" } ] ).toArray()

for (let e2 of arr2) {
  for (i = 0; i < doc1.Elements.length; i++) {
      if (doc1.Elements[i].id == e2.ElementsOverride.id) {
          doc1.Elements[i].Enabled = e2.ElementsOverride.Enabled
          doc1.Identifier = e2.Identifier
      }
  }
}

The output is the doc1 document.

Upvotes: 0

whoami - fakeFaceTrueSoul
whoami - fakeFaceTrueSoul

Reputation: 17915

You can try below query :

db.Collection1.aggregate([
    /** get only one required doc from Collection1 */
    { $match: { _id: 1 } },
    /** Join relative doc from Collection2 */
    {
        $lookup:
        {
            from: "Collection2",
            pipeline: [
                {
                    $match:
                    {
                        $expr:
                            { $eq: ["$Identifier", "identifier"] }
                    }
                }
            ],
            as: "data"
        }
    },
    /** As lookup will default to an array of objects getting an object out of array */
    { $unwind: '$data' },
    /** Replacing existing elements field of Collection1 & adding Identifier field to existing doc */
    {
        $addFields: {
            Identifier: '$data.Identifier', Elements:
            {
                $reduce: {
                    input: { $reverseArray: { $setUnion: ["$Elements", "$data.ElementsOverride"] } },
                    initialValue: [],
                    in: { $concatArrays: ["$$value", { $cond: [{ $in: ['$$this.id', '$$value.id'] }, [], ['$$this']] }] }
                }
            }
        }
    },
    /** removing unnecessary field created at lookup stage */
    { $project: { data: 0 } }
])

Test : MongoDB-Playground

Upvotes: 1

Related Questions