在查询中合并变更集文档

时间:2012-12-27 09:47:10

标签: mongodb

我已经记录了mongo数据库中信息系统的更改。每次设置或更改一组值时,记录都会保存在mongo数据库中。

更改集合采用以下格式:

{ "user_id": 1, "timestamp": { "date" : "2010-09-22 09:28:02", "timezone_type" : 3, "timezone" : "Europe/Paris" } }, "changes: { "fieldA": "valueA", "fieldB": "valueB", "fieldC": "valueC" } }
{ "user_id": 1, "timestamp": { "date" : "2010-09-24 19:01:52", "timezone_type" : 3, "timezone" : "Europe/Paris" } }, "changes: { "fieldA": "new_valueA", "fieldB": null, "fieldD": "valueD" } }
{ "user_id": 1, "timestamp": { "date" : "2010-10-01 11:11:02", "timezone_type" : 3, "timezone" : "Europe/Paris" } }, "changes: { "fieldD": "new_valueD" } }

当然,每个用户有数千条记录,具有代表数百万条记录的不同属性。我想要做的是在给定时间查看用户状态。例如,2010-09-30的user_id 1将是

fieldA: new_valueA
fieldC: valueC
fieldD: valueD

这意味着我需要将给定用户的给定日期之前的所有更改展平为单个记录。我可以直接在mongo中这样做吗?

编辑:我使用的是mongodb的2.0版本,因此无法从聚合框架中受益。

编辑:听起来我找到了问题的答案。

var mapTimeAndChangesByUserId = function() { 
    var key = this.user_id;
    var value = { timestamp: this.timestamp.date, changes: this.changes };
    emit(key, value);
}

var reduceMergeChanges = function(user_id, changeset) {
    var mergeFunction = function(a, b) { for (var attr in b) a[attr] = b[attr]; };
    var result = {};

    changeset.forEach(function(e) { mergeFunction(result, e.changes); }); 

    return { timestamp: changeset.pop().timestamp, changes: result };
}

reduce函数按照它们的顺序合并更改并返回结果。

db.user_change.mapReduce(
    mapTimeAndChangesByUserId, 
    reduceMergeChanges,
    { 
        out:   { inline: 1 },
        query: { user_id: 1, "timestamp.date": { $lt: "2010-09-30" } },
        sort:  { "timestamp.date": 1 }
    });
'results' : [
    "_id": 1,
    "value": {
        "timestamp": "2010-09-24 19:01:52",
        "changes": {
            "fieldA": "new_valueA",
            "fieldB": null,
            "fieldC": "valueC",
            "fieldD": "valueD"
        }
    }
]

对我来说没问题。

1 个答案:

答案 0 :(得分:1)

你可以写一个MR来做这件事。

由于字段很像标签,你可以在这里修改一个很好的食谱计数标签示例:http://cookbook.mongodb.org/patterns/count_tags/当然不是计算你想要应用的最新值(假设因为你的问题不清楚)对于那个领域。

让我们来看看地图功能:

map = function() {
    if (!this.changes) {
        // If there were not changes for some reason lets bail this record
        return;
    }

    // We iterate the changes
    for (index in this.changes) {
        emit(index /* We emit the field name */, this.changes[index] /* We emit the field value */);
    }
}

现在为我们减少:

reduce = function(values){
    // This part is dependant upon your input query. If you add a sort of 
    // date (ts) DESC then you will prolly want the first index (0) not the last as
    // gathered here by values.length
    return values[values.length];
}

这将为每个类型的字段更改输出一个文档:

{
    _id: your_field_ie_fieldA,
    value: whoop
}

然后你可以迭代(最有可能)在线输出的结尾,然后你可以进行更改。

这当然是其中一种方式,并不是为了与您的应用完全一致运行,但这取决于您所处理的数据的大小;它可以非常接近。

我不确定groupdistinct是否可以在此运行,但它看起来可能是:http://docs.mongodb.org/manual/reference/method/db.collection.group/#db-collection-group但是我应该注意到该组基本上是一个MR包装但你可以做类似的东西(未经测试就像上面的MR):

db.col.group( {
                   key: { 'changes.fieldA': 1, // the rest of the fields },
                   cond: { 'timestamp.date': { $gt: new Date( '01/01/2012' ) } },
                   reduce: function ( curr, result ) { },
                   initial: { }
                } )

但它确实需要你定义键而不是只是以可编程的方式迭代它们(也许是更好的方法)。