How to use MongoDB aggregate to get the first of each group, including nulls?

寵の児 提交于 2019-12-23 05:46:19

问题


In my MongoDB people collection I need to filter people with the same 'alias' property value, keeping the first one of them, and also keeping all people with a null 'alias'.

Some sample people data:

{ "_id" : "1", "flag" : true,  "name" : "Alice",    "alias" : null },
{ "_id" : "2", "flag" : true,  "name" : "Bob",      "alias" : "afa776bea788cf4c" },
{ "_id" : "3", "flag" : true,  "name" : "Bobby",    "alias" : "afa776bea788cf4c" },
{ "_id" : "4", "flag" : true,  "name" : "Cristina", "alias" : null },
{ "_id" : "5", "flag" : false, "name" : "Diego",    "alias" : null },
{ "_id" : "6", "flag" : true,  "name" : "Zoe",      "alias" : "2211293acc82329a" },

This is the result I expect:

{ "_id" : "1", "name" : "Alice",    "alias" : null },
{ "_id" : "2", "name" : "Bob",      "alias" : "afa776bea788cf4c" },
{ "_id" : "4", "name" : "Cristina", "alias" : null },
{ "_id" : "6", "name" : "Zoe",      "alias" : "2211293acc82329a" },

I've come with this initial query:

db.people.aggregate({ $group: { _id: '$alias', alias: { $first: '$alias' } } })

The first problem I face is that this returns only _id and alias fields, but I need all of them...

UPDATE: I have changed a bit sample data to better reflect my use case, since @user3100115 answer solves the issue for old sample data, but not for real data.

What I did change:

  • add one more document ("Cristina") with a null alias (my documents all have "alias" field), since I need all documents with a null alias value to be returned, and not just the first one.

  • add one more boolean property ("flag"), which I need to be able to match, too... I.e.: using find() I'd do: db-people.find({flag:true}), but I don't understand how to filter with more fields with aggregate()...

Please tell me if you think I should better place a new question...


回答1:


You can use $first to return the the _id value in the $group stage.

db.people.aggregate([ 
    { '$match': { 'flag': true } }, 
    { '$project': {
        'name': 1,          
        'alias': { 
            '$cond': [
                { '$eq': [ '$alias', null ] }, 
                '$_id', 
                '$alias' 
            ]
        }
    }},
    { '$group': {
        '_id': '$alias',         
        'name':  { '$first': '$name' },          
        'id': { '$first': '$_id' }       
    }}, 
    { '$project': {
        'alias': {
            '$cond': [ 
                { '$eq': [ '$id', '$_id' ] }, 
                null, 
               '$_id' 
            ]
        }, 
        'name': 1,
        '_id': '$id'
    }}
])

Which returns:

{ "_id" : "6", "name" : "Zoe", "alias" : "2211293acc82329a" }
{ "_id" : "4", "name" : "Cristina", "alias" : null }
{ "_id" : "2", "name" : "Bob", "alias" : "afa776bea788cf4c" }
{ "_id" : "1", "name" : "Alice", "alias" : null }



回答2:


If you need all the fields including the original _id you need to specify them all in the grouping and then additionally do a projection:

db.entries.aggregate([{ $group: { _id: '$alias', alias: { $first: '$alias' }, name: {$first: '$name'}, id: {$first: '$_id'} } }, {$project: {_id: '$id', name: '$name', alias: '$alias'}}])



回答3:


If you need all your fields you can use $$ROOT to references the root document. It will keep all fields of the document in a one field.

Person.aggregate([
    {
        $project:
        {
            alias: {$ifNull: ['$alias', "$_id"] },
            name: 1,
            document: "$$ROOT"
        }
    },
    { $group: { _id: "$alias", name: {$first: "$name"}, document: {$first: "$document"}}},
    {
        $project: { _id:0, document: 1}
    }
], function (err, documents) {
    var result = documents.map(function(doc){
        return doc.document;
    }); 
});


来源:https://stackoverflow.com/questions/34375163/how-to-use-mongodb-aggregate-to-get-the-first-of-each-group-including-nulls

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!