MongoDB update/insert 记录并递增匹配的数组元素

MongoDB update/insert document and Increment the matched array element

我将 Node.js 和 MongoDB 与 monk.js 一起使用,我想以最少的方式每小时记录一个文档,例如:

最终文档:

{ time: YYYY-MM-DD-HH, log: [ {action: action1, count: 1 }, {action: action2, count: 27 }, {action: action3, count: 5 } ] }

完整的文档应该通过增加一个值来创建。

例如有人在这个小时第一次访问网页并且 action1 的增量应该创建以下带有查询的文档:

{ time: YYYY-MM-DD-HH, log: [ {action: action1, count: 1} ] }

其他用户在这一小时内访问其他网页和文档应扩展为:

{ time: YYYY-MM-DD-HH, log: [ {action: action1, count: 1}, {action: action2, count: 1} ] }

访问不同的网页时,count 中的值应该递增。

目前我为每个动作创建了一个文档:

tracking.update({ time: moment().format('YYYY-MM-DD_HH'), action: action, info: info }, { $inc: {count: 1} }, { upsert: true }, function (err){}

monk.js / mongodb 这可能吗?

编辑: 谢谢你。您的解决方案看起来干净优雅,但看起来我的服务器无法处理它,或者我不想让它工作。

我写了一个极其肮脏的解决方案,以动作名称为键:

tracking.update({ time: time, ts: ts}, JSON.parse('{ "$inc": {"'+action+'": 1}}') , { upsert: true }, function (err) {});

是的,这是一个很有可能并且经过深思熟虑的问题。我对该方法所做的唯一变化是将 "time" 值计算为真正的 Date 对象(在 MongoDB 中非常有用,并且也具有可操作性)但只是 "round" 具有基本日期数学的值。您可以使用 "moment.js" 得到相同的结果,但我发现数学很简单。

这里的另一个主要考虑是将数组 "push" 操作与可能的 "updsert" 文档操作混合可能是一个真正的问题,因此最好使用 "multiple" 更新语句来处理这个问题,只有您想要的条件才会改变任何东西。

最好的方法是 MongoDB Bulk Operations.

考虑一下您的数据是这样的:

{ "timestamp": 1439381722531, "action": "action1" }

其中 "timestamp" 是精确到毫秒的纪元时间戳值。所以这个处理看起来像:

 // Just adding for the listing, assuming already defined otherwise
var payload = { "timestamp": 1439381722531, "action": "action1" };

// Round to hour
var hour = new Date(
    payload.timestamp - ( payload.timestamp % ( 1000 * 60 * 60 ) )
);

// Init transaction
var bulk = db.collection.initializeOrderedBulkOp();

// Try to increment where array element exists in document
bulk.find({ 
    "time": hour,
    "log.action": payload.action
}).updateOne({
    "$inc": { "log.$.count": 1 }
});

// Try to upsert where document does not exist
bulk.find({ "time": hour }).upsert().updateOne({
    "$setOnInsert": {
        "log": [{ "action": payload.action, "count": 1 }]
    }
});

// Try to "push" where array element does not exist in matched document
bulk.find({
    "time": hour,
    "log.action": { "$ne": payload.action }
}).updateOne({
    "$push": { "log": { "action": payload.action, "count": 1 } }
});

bulk.execute();

因此,如果您查看那里的逻辑,那么您会发现对于文档的任何给定状态,无论存在与否,这些语句中的 "one" 永远是唯一可能的。从技术上讲,带有 "upsert" 的语句实际上可以匹配存在的文档,但是使用的 $setOnInsert 操作确保不会进行 no 更改,除非action 实际上"inserts"一个新文件。

由于所有操作都在 "Bulk" 中触发,因此唯一一次联系服务器是在 .execute() 调用中。因此,尽管有多个操作,但只有 "one" 请求到服务器并且只有 "one" 响应。实际上是"one"请求

这样条件都满足了:

  1. 为当前期间不存在的文档创建一个新文档,并将初始数据插入数组。

  2. 在当前 "action" 分类不存在的数组中添加一个新项目并添加一个初始计数。

  3. 在执行语句时增加数组中指定动作的计数属性。

总而言之,是可以的,也是存储的好主意,只要动作分类在一段时间内不会增长太大(应使用 500 个数组元素作为最大指南)并且更新非常快每个时间样本都高效且自包含在一个文档中。

该结构也很好,非常适合其他查询和可能的其他聚合目的。