MongoDB:查找具有相似日期的子文档实例

时间:2015-05-11 08:40:05

标签: mongodb mongodb-query

我有一个像

这样的结构的mongodb集合
[
  {
    name: "name1",
    instances: [{value:1, score:2, date:<ISODate>},
                {value:2, score:5, date:<ISODate>},
                {value:2.5, score:9, date:<ISODate>},
                ...]
  },
  {
    name: "name2",
    instances: [{value:6, score:3, date:<ISODate>},
                {value:1, score:6, date:<ISODate>},
                {value:3.7, score:5.2, date:<ISODate>},
                ...]
  },
  ...
]

我想查找实例的日期是否来自同一天的同一name的两个(或更多)实例,并返回这些实例。

稍后我想删除除了其中一个实例之外的所有实例,但作为一个开始,我希望能够找到它们。

我尝试按日期汇总和分组,但无法弄清楚如何只比较当天(而不是整个日期)。

2 个答案:

答案 0 :(得分:4)

假设您为了演示目的在测试集合中插入了以下测试文档:

db.test.insert([
{
    "name" : "name1",
    "instances" : [ 
        {
            "value" : 1,
            "score" : 2,
            "date" : ISODate("2015-03-04T00:00:00.000Z")
        }, 
        {
            "value" : 2,
            "score" : 5,
            "date" : ISODate("2015-04-01T00:00:00.000Z")
        }, 
        {
            "value" : 2.5,
            "score" : 9,
            "date" : ISODate("2015-03-05T00:00:00.000Z")
        }
    ]
},
{
    "name" : "name2",
    "instances" : [ 
        {
            "value" : 6,
            "score" : 3,
            "date" : ISODate("2015-03-05T00:00:00.000Z")
        }, 
        {
            "value" : 1,
            "score" : 6,
            "date" : ISODate("2015-03-04T00:00:00.000Z")
        }, 
        {
            "value" : 3.7,
            "score" : 5.2,
            "date" : ISODate("2015-02-04T00:00:00.000Z")
        }
    ]
},
{
    "name" : "name1",
    "instances" : [ 
        {
            "value" : 6,
            "score" : 3,
            "date" : ISODate("2015-03-05T00:00:00.000Z")
        }, 
        {
            "value" : 1,
            "score" : 6,
            "date" : ISODate("2015-03-04T00:00:00.000Z")
        }, 
        {
            "value" : 3.7,
            "score" : 5.2,
            "date" : ISODate("2015-02-04T00:00:00.000Z")
        }
    ]
}
])

然后以下聚合将完成这项工作:

var pipeline = aggregate([
    {
        "$unwind": "$instances"
    },
    {
        "$group": {
            "_id": {
                "name": "$name",
                "year": {
                    "$year": "$instances.date"
                },
                "month": {
                    "$month": "$instances.date"
                },
                "day": {
                    "$dayOfYear": "$instances.date"
                }
            },
            "count": {
                "$sum": 1
            },
            "data": {
                "$addToSet": "$$ROOT"
            }
        }
    },
    {
        "$match": {
            "count": {
                "$gt": 1
            }
        }
    },
    {
        "$unwind": "$data"
    },
    {
        "$group": {
            "_id": {
                "name": "$data.name",
                "_id": "$data._id"
            }
        }
    },
    {
        "$project": {
            "_id": "$_id._id",
            "name": "$_id.name"
        }
    }
]);
db.test.aggregate(pipeline);

<强>输出

/* 0 */
{
    "result" : [ 
        {
            "_id" : ObjectId("55506d0a180e849972939056"),
            "name" : "name1"
        }, 
        {
            "_id" : ObjectId("55506d0a180e849972939058"),
            "name" : "name1"
        }
    ],
    "ok" : 1
}

上述聚合管道具有$unwind操作作为第一步,它从输入文档解构instances数组字段以输出每个元素的文档。每个输出文档都使用元素值替换数组。

下一个管道阶段$group"name""instances.date"字段对文档进行分组(使用 Date Aggregation Operators <将日期字段拆分为三个字段/ strong>),计算每个组的count字段,并为每个唯一namedate(截至日期部分)输出文档。组data中有一个额外的数组字段,它使用系统变量$$ROOT来存储当前正在聚合管道阶段处理的原始根文档,即顶级文档。使用$addToSet数组运算符将此根文档添加到数组中。

在管道的下方,您需要通过使用$match管道按照名称和日期进行分组来过滤那些重复的文档,其中指定的计数应该大于1。

然后在data字段上应用另一个$unwind操作,以提取重复项的实际_idname,这些副本将再次分组以进一步简化文档。

通过修改字段来构建最终文档结构需要额外的$project管道阶段。

使用聚合结果游标然后使用forEach()方法迭代结果并删除其他重复文档:

var cur = db.test.aggregate(pipeline);
cur.forEach(function (doc){
    var count = 0;
    if (count != 0){
        db.test.remove({"_id": doc._id});
    }
    count++;
});

另一种选择是将$out运算符作为最终的管道阶段,将聚合管道返回的文档写入指定的集合,然后可以查询并执行删除:

var cur = db.outputcollection.find();
cur.forEach(function (doc){
    var count = 0;
    if (count != 0){
        db.test.remove({"_id": doc._id});
    }
    count++;
});

答案 1 :(得分:3)

如果我理解得那么你应该$unwind然后$group按日期和实例,filtering out只有一个文档的组。这样的事情(我现在无法访问MongoDB - 谨防拼写错误):

db.coll.aggregate([
  {$unwind: "$instances"},
  {$group: { _id: { name:"$name", day:{$dayOfYear:"$date"}, year:{$year:"$date"}}, count: {$sum: 1} }},
  {$match: {count: {$gt: 1}}}
])