如果尚未给出对象属性,则将对象添加到对象数组

时间:2017-08-30 15:15:23

标签: mongodb mongoose

使用案例

我有一个集合band_profiles,我有一个集合band_profiles_history。历史收集应该每24小时存储一次band_profile快照,因此我使用MongoDB推荐的历史跟踪格式:每个月+年是它自己的文档,在对象数组中我将存储bandProfile快照以及当前日期这个月。

我的模特:

band_profiles_history中的文档如下所示:

{
    "_id" : ObjectId("599e3bc406955db4cbffe0a8"),
    "month" : 7,
    "tag_lowercased" : "9yq88gg",
    "year" : 2017,
    "values" : [
        {
            "_id" : ObjectId("599e3bc41c073a7418fead91"),
            "profile" : {
                "_id" : ObjectId("5989a65d0f39d9fd70cde1fe"),
                "tag" : "9YQ88GG",
                "name_normalized" : "example name1",
            },
            "day" : 1
        },
        {
            "_id" : ObjectId("599e3bc41c073a7418fead91"),
            "profile" : {
                "_id" : ObjectId("5989a65d0f39d9fd70cde1fe"),
                "tag" : "9YQ88GG",
                "name_normalized" : "new name",
            },
            "day" : 2
        }
    ]
}

band_profiles中的文件:

{
    "_id" : ObjectId("5989a6190f39d9fd70cddeb1"),
    "tag" : "9V9LRGU",
    "name_normalized" : "example name",
    "tag_lowercased" : "9v9lrgu",
}

这就是我现在将文档升级到band_profiles_history的方式:

BandProfileHistory.update(
  { tag_lowercased: tag, year, month},
  { $push: {
        values: { day, profile }
    }
  }, 
  { upsert: true }
)

我的问题:

我只想每天插入一张快照。现在它总是将一个新对象推送到对象数组values,无论我是否已经拥有该对象。如果当天没有对象,我怎么能实现只推送那个对象呢?

3 个答案:

答案 0 :(得分:1)

将猫鼬搁置一会儿:

有一个操作addToSet,如果数组尚不存在,它将向数组添加元素。

警告:

  

如果值是文档,如果数组中的现有文档与要添加的文档完全匹配,则MongoDB确定文档是重复的;即,现有文档具有完全相同的字段和值,并且字段的顺序相同。因此,字段顺序很重要,您无法指定MongoDB仅比较文档中字段的子集来确定文档是否与现有数组元素重复。

由于您尝试添加整个文档,因此您受到此限制。

所以我看到了以下解决方案:

解决方案1:

读入数组,查看它是否包含您想要的元素,如果没有,则将其推送到values push数组。

这有缺点 NOT 是一个原子操作意味着你最终可能会重复。如果您运行定期清理作业以从每个文档的此字段中删除重复项,则可以接受这一点。

由您决定是否可以接受。

解决方案2:

假设您将字段_id放在values字段的子文档中,请停止操作。假设mongoose正在为你做这件事(因为根据我的理解,这样做)阻止它像这里所说的那样做:Stop mongoose from creating _id for subdocument in arrays

接下来,您需要确保文档中的字段始终具有相同的顺序,因为在比较上述引文中所述的addToSet操作中的文档时,顺序很重要。

解决方案3

band_profiles_history的架构更改为:

{
    "_id" : ObjectId("599e3bc406955db4cbffe0a8"),
    "month" : 7,
    "tag_lowercased" : "9yq88gg",
    "year" : 2017,
    "values" : {
       "1": { "_id" : ObjectId("599e3bc41c073a7418fead91"),
            "profile" : {
                "_id" : ObjectId("5989a65d0f39d9fd70cde1fe"),
                "tag" : "9YQ88GG",
                "name_normalized" : "example name1"
            }
        },
        "2": {
            "_id" : ObjectId("599e3bc41c073a7418fead91"),
            "profile" : {
                "_id" : ObjectId("5989a65d0f39d9fd70cde1fe"),
                "tag" : "9YQ88GG",
                "name_normalized" : "new name"
            }
        }

}

请注意,day字段成为values上子文档的键。另请注意,values现在是Object而不是Array

除非values.<day>不存在,否则您无法运行仅更新values.<day>的更新查询。

我个人不喜欢这个,因为它使用了JSON不允许重复键支持架构这一事实。

答案 1 :(得分:1)

首先,遗憾的是mongodb不支持集合数组中字段的唯一性。您可以看到major bug已开启7年且尚未关闭(我认为这是一种耻辱)。

您可以从这里做的事情是有限的,所有都在应用程序级别。我有同样的问题,并在应用程序级别解决它。做这样的事情:

  1. 首先阅读包含文档_idvalues.day的文档。
  2. 如果您在步骤1中的读数返回null,则表示给定日期值数组中没有记录,因此您可以push新值(我假设band_profile_history已记录{{1 }} value。。
  3. 如果您在步骤1中的阅读返回文档,则表示值数组具有给定日期的记录。在这种情况下,您可以对_id运算符使用set操作。
  4. 像其他人说的那样,它们不是原子的,但是当你在应用程序级别处理问题时,你可以制作一大堆代码同步。在3个查询中,将在mongodb上运行2个查询。如下所示:

    $

    如果返回null:

    db.getCollection('band_profiles_history').find({"_id": "1", "values.day": 3})

    如果返回不为null:

    db.getCollection('band_profiles_history').update({"_id": "1"}, {$push: {"values": {<your new band profile history for given day>}}})

答案 2 :(得分:0)

检查对象是否为空

{ field: {$exists: false} }

或者如果是数组

 { field: {$eq: []} }    

Mongoose还支持field: {type: Date},因此您可以使用它来计算天数,并仅针对当前日期进行更新。