如何参考以前的状态更新文档?

时间:2019-07-03 17:09:44

标签: mongodb

是否可以在更新操作期间引用根文档,使得这样的文档:

{"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,但是如何实现呢?

理想情况下,我不想显式引用每个字段。我希望在不知道其他字段是什么的情况下推送根文档(减去_idprevious字段)。

3 个答案:

答案 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,如此复杂的数据模型无缘无故使您的生活变得无限复杂。

考虑一下您真正想要实现的目标。您想要关联一个或多个互连系列中的不同值,这些值将被连续写入到集合中。

将其存储在一个文档中会附带一些字符串。虽然一开始似乎很合理,但让我举几个例子:

  • 如果不知道名称的价值,如何获取最新文档?
  • 您如何处理非常大的系列,使文档达到16MB的限制?
  • 增加的复杂性有什么好处?

先简化

因此,假设您暂时只有一个系列。它变得简单

[{
  "_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值。

接下来,我们将在seriests上创建索引,因为我们在查询中将需要它:

> 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":[]
    })