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.
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.
$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).
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.
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.
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]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With