Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB - $project nested document to root level

Tags:

mongodb

Is it possible without writing all the sub-document's fields?

Let's say I have the following document structure:

{
  field1: a,
  subdoc: {
          field2: b,
          field3: c
  }
}

I want to use $project in order to get the suboc at root level:

{
  field1: a,
  field2: b,
  field3: c              
}

This is just an example with 2 fields in the subdoc, my real document has many fields and more may be added or deleted in the future, so I want the $project to be more dynamic and not to specify all the fields separately.

like image 978
TomG Avatar asked Dec 23 '15 08:12

TomG


People also ask

How do I access a nested document in MongoDB?

Accessing embedded/nested documents – In MongoDB, you can access the fields of nested/embedded documents of the collection using dot notation and when you are using dot notation, then the field and the nested field must be inside the quotation marks.

What is $$ root MongoDB?

$replaceRoot. Replaces the input document with the specified document. The operation replaces all existing fields in the input document, including the _id field. You can promote an existing embedded document to the top level, or create a new document for promotion (see example).

What does $project do in MongoDB?

The $project takes a document that can specify the inclusion of fields, the suppression of the _id field, the addition of new fields, and the resetting of the values of existing fields. Alternatively, you may specify the exclusion of fields. Specifies the inclusion of a field.

What is nested collection in MongoDB?

Or in other words, when a collection has a document, this document contains another document, another document contains another sub-document, and so on, then such types of documents are known as embedded/nested documents. In MongoDB, you can only nest document up to 100 levels.


1 Answers

For MongoDB 3.6 and newer, use aggregation framework with a $replaceRoot pipeline that can be applied in conjunction with the $mergeObjects operator as the newRoot expression.

This expression

{ "$mergeObjects": ["$subdoc", "$$ROOT"] }

will merge the top level fields in the document with the ones in the subdoc embedded fields so in the end your aggregate operation will be as follows:

db.collection.aggregate([
    { "$replaceRoot": { 
        "newRoot": { 
            "$mergeObjects": [ "$subdoc", "$$ROOT" ] 
        } 
    } },
    { "$project": { "subdoc": 0 } }  
])

Otherwise you would need a mechanism to get all the dynamic keys that you need to assemble the dynamic $project document. This is possible through Map-Reduce. The following mapreduce operation will populate a separate collection with all the keys as the _id values:

mr = db.runCommand({
    "mapreduce": "my_collection",
    "map" : function() {
        for (var key in this.subdoc) { emit(key, null); }
    },
    "reduce" : function(key, stuff) { return null; }, 
    "out": "my_collection" + "_keys"
})

To get a list of all the dynamic keys, run distinct on the resulting collection:

db[mr.result].distinct("_id")
["field2", "field3", ...]

Now given the list above, you can assemble your $project aggregation pipeline document by creating an object that will have its properties set within a loop. Normally your $project document will have this structure:

var project = {
    "$project": {
        "field1": 1,
        "field2": "$subdoc.field2",
        "field3": "$subdoc.field3"
    }
};

So using the above list of subdocument keys, you can dynamically construct the above using JavaScript's reduce() method:

var subdocKeys = db[mr.result].distinct("_id"),
    obj = subdocKeys.reduce(function (o, v){
      o[v] = "$subdoc." + v;
      return o;
    }, { "field1": 1 }),
    project = { "$project": obj };

db.collection.aggregate([project]);
like image 68
chridam Avatar answered Nov 16 '22 00:11

chridam