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"请求
这样条件都满足了:
为当前期间不存在的文档创建一个新文档,并将初始数据插入数组。
在当前 "action" 分类不存在的数组中添加一个新项目并添加一个初始计数。
在执行语句时增加数组中指定动作的计数属性。
总而言之,是可以的,也是存储的好主意,只要动作分类在一段时间内不会增长太大(应使用 500 个数组元素作为最大指南)并且更新非常快每个时间样本都高效且自包含在一个文档中。
该结构也很好,非常适合其他查询和可能的其他聚合目的。
我将 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"请求
这样条件都满足了:
为当前期间不存在的文档创建一个新文档,并将初始数据插入数组。
在当前 "action" 分类不存在的数组中添加一个新项目并添加一个初始计数。
在执行语句时增加数组中指定动作的计数属性。
总而言之,是可以的,也是存储的好主意,只要动作分类在一段时间内不会增长太大(应使用 500 个数组元素作为最大指南)并且更新非常快每个时间样本都高效且自包含在一个文档中。
该结构也很好,非常适合其他查询和可能的其他聚合目的。