Mongoose findOneAndUpdate:创建然后更新嵌套数组

时间:2018-03-09 23:58:57

标签: javascript node.js mongoose mlab

我有一个程序,我从服务器请求天气数据,处理数据,然后使用mongoose将其保存到mlab帐户。我正在收集10年的数据,但我要求数据的API仅允许一次请求大约一年。

我正在使用findOndAndUpdate为每个气象站创建/更新文档,但在更新数据对象中的数组时遇到问题。 (可能不是描述它的最佳方式......)

例如,这是模型:

    const stnDataSchema = new Schema(
       { 
        station: { type: String, default: null }, 
        elevation: { type: String, default: null },
        timeZone: { type: String, default: null },
        dates: {},
        data: {}
       }, 
       { collection: 'stndata' },
       { runSettersOnQuery: true }
     )

其中dates对象如下所示:

    dates: ["2007-01-01",
    "2007-01-02",
    "2007-01-03",
    "2007-01-04",
    "2007-01-05",
    "2007-01-06",
    "2007-01-07",
    "2007-01-08",
    "2007-01-09"]

和这样的数据对象:

    "data": [
       {
        "maxT": [
            0,
            null,
            4.4,
            0,
            -2.7,
            etc.....

我想要发生的是当我运行findOneAndUpdate时,我希望找到基于工作站的文档,然后将新的maxT值和日期附加到相应的数组。我有它为日期数组工作,但我遇到了数据数组的问题,因为我更新的元素是嵌套的。 我试过这个:

    const update = {
    $set: {'station': station, 'elevation': elevation, 'timeZone': timeZone},
    $push: {'dates': datesTest, 'data.0.maxT': testMaxT}};
    StnData.findOneAndUpdate( query, update, {upsert: true} ,
    function(err, doc) {
    if (err) {
     console.log("error in updateStation", err)
     throw new Error('error in updateStation')
   }
   else {
     console.log('saved')

但是像mb这样输出到mlab:

    "data": {
      "0": {
          "maxT": [
            "a",
            "b",

问题是我得到一个“0”而不是一个元素的数组。我试过'data [0] .maxT'但是当我这样做时没有任何反应。

问题是我第一次运行工作站的数据时,我想在我的第三个代码块中创建一个包含格式数据对象的新文档,然后在后续运行中,一旦该文档已经存在,就更新具有新值的maxT数组。有什么想法吗?

1 个答案:

答案 0 :(得分:1)

您正在获得此输出:

 "data": {
    "0": {
      "maxT": [
        "a",
        "b",

因为您正在申请该文件。处理文档数组时,Upserting会有点复杂。

更新数组时,MongoDB知道data.0引用数组中的第一个元素。但是,在插入时,MongoDB无法判断它是否是一个数组或一个对象。所以它假设它是一个对象。因此,不是插入["val"],而是插入{"0": "val"}

最简单的解决方案

不要使用upsert。为每个新气象站插入文档,然后使用findOndAndUpdate将值推送到文档中的数组中。只要您第一次正确插入数组,就可以在没有它们变成对象的情况下推送它们。

如果data只包含一个对象

,则

替代简单解决方案

从您的问题来看,您似乎只有data中的一个对象。如果是这种情况,您可以将maxT数组放在顶层,而不是数组中单个文档的属性。然后它会像dates一样行动。

更复杂的MongoDB 3.6解决方案

如果真的离不开upsert,MongoDB 3.6引入了过滤后的位置运算符$[<identifier>]。您可以使用此运算符更新数组中与查询匹配的特定元素。 Unlike the simple positional operator $,只要使用完全匹配,就可以使用新的$[<identifier>]运算符进行upsert。

您可以在此处详细了解此运算符:https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/

因此,您的data个对象需要有一个可以完全匹配的字段(例如name)。示例查询看起来像这样:

let query = {
  _id: 'idOfDocument', 
  data: [{name: 'subobjectName'}] // Need this for an exact match
}

let update = {$push: {'data.$[el].maxT': testMaxT}}

let options = {upsert: true, arrayFilters: [{'el.name': 'subobjectName'}]}

StnData.findOneAndUpdate(query, update, options, callbackFn)

正如您所看到的,这会增加更多的复杂性。忘记尝试做upsert会容易得多。只需执行一次插入然后更新。

此外,mLab目前不支持MongoDB 3.6。因此,当使用mLab时,这种方法不可行,直到支持3.6。