MongoDb:如何对日期字段进行聚合、分组和排序?

标签 mongodb mongodb-query aggregation-framework

在我的 MongoDB 人员集合中,我需要过滤具有相同“别名”属性值的人员,保留其中第一个,并保留所有具有空“别名”的人员。

数据是这样的:

{ "_id" : "1", "flag" : true,  "name" : "Alice",    "alias" : null, "dateOfBirth": new ISODate('1995-12-27T00:00:00.000Z') },
{ "_id" : "2", "flag" : true,  "name" : "Bob",      "alias" : "4c", "dateOfBirth": new ISODate('1996-12-27T00:00:00.000Z') },
{ "_id" : "3", "flag" : true,  "name" : "Bobby",    "alias" : "4c", "dateOfBirth": new ISODate('1997-12-27T00:00:00.000Z') },
{ "_id" : "4", "flag" : true,  "name" : "Cristina", "alias" : null, "dateOfBirth": new ISODate('1998-12-27T00:00:00.000Z') },
{ "_id" : "5", "flag" : false, "name" : "Diego",    "alias" : null, "dateOfBirth": new ISODate('1999-12-27T00:00:00.000Z') },
{ "_id" : "6", "flag" : true,  "name" : "Zoe",      "alias" : "22", "dateOfBirth": new ISODate('2000-12-27T00:00:00.000Z') }

这是我的查询:

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'
    }}
])

返回结果:

{ "_id" : "6", "name" : "Zoe",      "alias" : "22" }
{ "_id" : "4", "name" : "Cristina", "alias" : null }
{ "_id" : "2", "name" : "Bob",      "alias" : "4c" }
{ "_id" : "1", "name" : "Alice",    "alias" : null }

到目前为止一切顺利。

更新:到目前为止,问题类似于 suggested duplicate question 。现在是不同的部分:

我需要根据“dateOfBirth”字段对其进行排序。
非常有信心,我将查询更改为:

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

但这给出了:

{ "_id" : "1", "name" : "Alice",    "dateOfBirth" : ISODate("1995-12-27T00:00:00Z"), "alias" : null }
{ "_id" : "6", "name" : "Zoe",      "dateOfBirth" : null, "alias" : "22" }
{ "_id" : "4", "name" : "Cristina", "dateOfBirth" : null, "alias" : null }
{ "_id" : "2", "name" : "Bob",      "dateOfBirth" : null, "alias" : "4c" }

当然,这是错误的:dateOfBirth 字段未通过 $group 阶段...

有什么线索可以让它通过吗?

最佳答案

实际上,这对我来说效果很好,我怀疑您正在运行的实际代码(不是该列表)中有一个拼写错误,缺少在某处包含“dateOfBirth”(或者可能拼写错误)。

但如果说这里有一个教训的话,那就应该是不要分开 $project$group阶段,因为引入另一个管道阶段不仅效率低下(这意味着额外传递数据),而且在指定要包含在管道中的数据时,这也是造成困惑的常见原因。

所以宁愿这样做:

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

这也利用 $ifNull作为自然测试,而不是在不需要时使用 $cond

当然会返回所需的结果:

{ "_id" : "1", "name" : "Alice", "dateOfBirth" : ISODate("1995-12-27T00:00:00Z"), "alias" : null }
{ "_id" : "2", "name" : "Bob", "dateOfBirth" : ISODate("1996-12-27T00:00:00Z"), "alias" : "4c" }
{ "_id" : "4", "name" : "Cristina", "dateOfBirth" : ISODate("1998-12-27T00:00:00Z"), "alias" : null }
{ "_id" : "6", "name" : "Zoe", "dateOfBirth" : ISODate("2000-12-27T00:00:00Z"), "alias" : "22" }

如果您想要“按出生日期排序”,请将排序移到 $group 阶段之前,$first 运算符将在那里完成所有工作。

关于MongoDb:如何对日期字段进行聚合、分组和排序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34517753/

相关文章:

node.js - NodeJS MongoDB 在聚合管道中传递参数

java - 在 Mongodb 中使用 View 的性能优势/劣势

node.js - 查询mongodb中另一个列表中包含的列表

mongodb - 使用更新查询将文档值从字符串更改为 ObjectId

mongodb - 从 MongoDB 中的 ObjectId 获取所有文档和项目时间戳

Java MongoClient - 如何查找文档并返回特定对象

javascript - meteor 发布复合和嵌套集合

java - MongoDB java通过带有字段的函数更新文档

java - 异常来自 Collection 的不同值 - CurrentBSONType 是 UNDEFINED

node.js - 聚合和分配随机获胜者的更好方法