是否可以在更新操作期间引用根文档,使得这样的文档:
{"name":"foo","value":1}
可以使用新值进行更新,并将完整的(先前的)文档推送到新字段中(创建更新历史记录):
{"name":"bar","value":2,"previous":[{"name:"foo","value":1}]}
以此类推。
{"name":"baz","value":3,"previous":[{"name:"foo","value":1},{"name:"bar","value":2}]}
我认为我必须在Mongo 4.2中使用新的aggregate set operator,但是如何实现呢?
理想情况下,我不想显式引用每个字段。我希望在不知道其他字段是什么的情况下推送根文档(减去_id
和previous
字段)。
答案 0 :(得分:2)
除了新的$set
运算符之外,使Mongo 4.2
的用例真正变得更容易的原因是,db.collection.update()
现在接受聚合管道,最终允许更新字段根据其当前值:
// { name: "foo", value: 1 }
db.collection.update(
{},
[{ $set: {
previous: {
$ifNull: [
{ $concatArrays: [ "$previous", [{ name: "$name", value: "$value" }] ] },
[ { name: "$name", value: "$value" } ]
]
},
name: "bar",
value: 2
}}],
{ multi: true }
)
// { name: "bar", value: 2, previous: [{ name: "foo", value: 1 }] }
// and if applied again:
// { name: "baz", value: 3, previous: [{ name: "foo", value: 1 }, { name: "bar", value: 2 } ] }
第一部分{}
是匹配查询,用于过滤要更新的文档(在我们的情况下可能是所有文档)。
第二部分[{ $set: { previous: { $ifNull: [ ... } ]
是更新聚合管道(请注意方括号表示使用聚合管道)
$set
是新的聚合运算符,别名为$addFields
。它用于使用当前文档中的值添加/替换新字段(在我们的示例中为"previous"
)。$ifNull
检查,我们可以确定文档中是否已经存在"previous"
(第一次更新不是这种情况)。"previous"
不存在(为null),那么我们必须创建它并使用一个元素数组来设置它:当前文档:[ { name: "$name", value: "$value" } ]
。"previous"
已经存在,那么我们将现有数组与当前文档连接($concatArrays
)。不要忘记{ multi: true }
,否则只会更新第一个匹配的文档。
正如您在问题中提到的"root"
一样,如果所有文档的架构都不相同(如果您无法确定应该使用哪些字段并将其推入"previous"
数组中),那么您可以使用代表当前文档的$$ROOT
变量并过滤掉"previous"
数组。在这种情况下,请将上一个查询中的两个{ name: "$name", value: "$value" }
替换为:
{ $arrayToObject: { $filter: {
input: { $objectToArray: "$$ROOT" },
as: "root",
cond: { $ne: [ "$$root.k", "previous" ] }
}}}
答案 1 :(得分:0)
Imho,如此复杂的数据模型无缘无故使您的生活变得无限复杂。
考虑一下您真正想要实现的目标。您想要关联一个或多个互连系列中的不同值,这些值将被连续写入到集合中。
将其存储在一个文档中会附带一些字符串。虽然一开始似乎很合理,但让我举几个例子:
因此,假设您暂时只有一个系列。它变得简单
[{
"_id":"foo",
"ts": ISODate("2019-07-03T17:40:00.000Z"),
"value":1
},{
"_id":"bar",
"ts": ISODate("2019-07-03T17:45:00.000"),
"value":2
},{
"_id":"baz",
"ts": ISODate("2019-07-03T17:50:00.000"),
"value":3
}]
假设名称是唯一的,我们可以将其用作_id
,从而可能保存索引。
您实际上只需做一个
> db.seriesa.find().sort({ts:-1})
{ "_id" : "baz", "ts" : ISODate("2019-07-03T17:50:00Z"), "value" : 3 }
{ "_id" : "bar", "ts" : ISODate("2019-07-03T17:45:00Z"), "value" : 2 }
{ "_id" : "foo", "ts" : ISODate("2019-07-03T17:40:00Z"), "value" : 1 }
假设您只想拥有两个最新值,则可以使用limit()
:
> db.seriesa.find().sort({ts:-1}).limit(2)
{ "_id" : "baz", "ts" : ISODate("2019-07-03T17:50:00Z"), "value" : 3 }
{ "_id" : "bar", "ts" : ISODate("2019-07-03T17:45:00Z"), "value" : 2 }
您是否真的需要将较早的值放在类似队列的数组中
db.seriesa.aggregate([{
$group: {
_id: "queue",
name: {
$last: "$_id"
},
value: {
$last: "$value"
},
previous: {
$push: {
name: "$_id",
value: "$value"
}
}
}
}, {
$project: {
name: 1,
value: 1,
previous: {
$slice: ["$previous", {
$subtract: [{
$size: "$previous"
}, 1]
}]
}
}
}])
现在,让我们说您拥有多个数据系列。基本上,有两种处理方法:将不同的系列放在不同的集合中或将所有系列放在一个集合中,并按字段进行区分,出于明显的原因,应将其编入索引。>
那么,什么时候使用什么?无论您是否要对所有系列进行聚合(或者稍后再进行聚合),它都会变得困难。如果这样做,则应将所有系列放入一个集合中。当然,我们必须稍微修改数据模型:
[{
"name":"foo",
"series": "a"
"ts": ISODate("2019-07-03T17:40:00.000Z"),
"value":1
},{
"name":"bar",
"series": "a"
"ts": ISODate("2019-07-03T17:45:00.000"),
"value":2
},{
"name":"baz",
"series": "a"
"ts": ISODate("2019-07-03T17:50:00.000"),
"value":3
},{
"name":"foo",
"series": "b"
"ts": ISODate("2019-07-03T17:40:00.000Z"),
"value":1
},{
"name":"bar",
"series": "b"
"ts": ISODate("2019-07-03T17:45:00.000"),
"value":2
},{
"name":"baz",
"series": "b"
"ts": ISODate("2019-07-03T17:50:00.000"),
"value":3
}]
请注意,出于演示目的,我回退了_id
的默认ObjectId值。
接下来,我们将在series
和ts
上创建索引,因为我们在查询中将需要它:
> db.series.ensureIndex({series:1,ts:-1})
现在我们的简单查询如下所示
> db.series.find({"series":"b"},{_id:0}).sort({ts:-1})
{ "name" : "baz", "series" : "b", "ts" : ISODate("2019-07-03T17:50:00Z"), "value" : 3 }
{ "name" : "bar", "series" : "b", "ts" : ISODate("2019-07-03T17:45:00Z"), "value" : 2 }
{ "name" : "foo", "series" : "b", "ts" : ISODate("2019-07-03T17:40:00Z"), "value" : 1 }
为了生成类似队列的文档,我们需要添加一个匹配状态
> db.series.aggregate([{
$match: {
"series": "b"
}
},
// other stages omitted for brevity
])
请注意,此处将使用我们之前创建的索引。
或者,我们可以通过在series
阶段将_id
用作$group
并将每个_id
替换为{{1} }
name
Stop Being Clever涉及MongoDB中的数据模型。我在野外看到的数据模型的大多数问题,以及在SO上看到的绝大多数问题,都源于有人试图变得聪明(通过过早优化)™。
除非我们谈论的是巨大的序列(由于您为方法设置了16MB的限制,否则不能这样),以上数据模型和查询都是高效的,而不会增加不必要的复杂性。
答案 2 :(得分:0)
addMultipleData: (req, res, next) => {
let name = req.body.name ? req.body.name : res.json({ message: "Please enter Name" });
let value = req.body.value ? req.body.value : res.json({ message: "Please Enter Value" });
if (!req.body.name || !req.body.value) { return; }
//Step 1
models.dynamic.findOne({}, function (findError, findResponse) {
if (findResponse == null) {
let insertedValue = {
name: name,
value: value
}
//Step 2
models.dynamic.create(insertedValue, function (error, response) {
res.json({
message: "succesfully inserted"
})
})
}
else {
let pushedValue = {
name: findResponse.name,
value: findResponse.value
}
let updateWith = {
$set: { name: name, value: value },
$push: { previous: pushedValue }
}
let options = { upsert: true }
//Step 3
models.dynamic.updateOne({}, updateWith, options, function (error, updatedResponse) {
if (updatedResponse.nModified == 1) {
res.json({
message: "succesfully inserted"
})
}
})
}
})
}
//This is the schema
var multipleAddSchema = mongoose.Schema({
"name":String,
"value":Number,
"previous":[]
})