How to flatten a subdocument into root level in Mo

2019-01-11 22:57发布

问题:

For example, if I have a document like this

{
  a: 1,
  subdoc: {
          b: 2,
          c: 3
          }
}

How can I convert it into a format like this? (without using project)

{
  a: 1,
  b: 2,
  c: 3
}

回答1:

You can use MongoDB projection i.e $project aggregation framework pipeline operators as well. (recommended way). If you don't want to use project check this link

db.collection.aggregation([{$project{ . . }}]);

Below is the example for your case:

db.collectionName.aggregate
([
    { $project: { a: 1, 'b': '$subdoc.b', 'c': '$subdoc.c'} }
]);

Gives you the output as you expected i.e.

    {
        "a" : 1,
        "b" : 2,
        "c" : 3
    }


回答2:

You can use $replaceRoot with a $addFields stage as follows:

db.collection.aggregate([
    { "$addFields": { "subdoc.a": "$a" } },
    { "$replaceRoot": { "newRoot": "$subdoc" }  }
])


回答3:

You can do this with the use of $set and $unset

db.collection.update({},{ $set: { "b": 2, "c": 3 }, $unset: { "subdoc": 1 } })

Of course if there are lots of documents in the collection, then much as above you need to loop the collection documents in order to get the values. MongoDB has no way of referring to a value of a field in an update operation alone:

db.collection.find({ "subdoc": {"$exists": 1 } }).forEach(function(doc) {
    var setDoc = {};
    for ( var k in doc.subdoc ) {
        setDoc[k] = doc.subdoc[k];
    }
    db.collection.update(
        { "_id": doc._id },
        { "$set": setDoc, "$unset": "subdoc" }
    );
})

That also employs some safe usage of $exists in order to make sure you are selecting documents where the "subdoc" key is actually there.



回答4:

I guess the easiest way is altering the result after it returns, using map.

collection.mapFunction = function(el) {
  el.b = el.subdoc.b;
  el.c = el.subdoc.c
  delete(el.subdoc);
  return el;
}

...

var result = await collection.aggregate(...).toArray();
result = result.map(collection.mapFunction);


回答5:

The best way is with $replaceRoot and $mergeObjects

let pipeline = [
    {
        "$replaceRoot":{
            "newRoot":{
                "$mergeObjects":[
                    {
                        "a":"$a"
                    },
                    "$subdoc"
                ]
            }
        }
    }
]
db.collection.aggregate(pipeline)