使用案例
我有一个集合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
,无论我是否已经拥有该对象。如果当天没有对象,我怎么能实现只推送那个对象呢?
答案 0 :(得分:1)
将猫鼬搁置一会儿:
有一个操作addToSet,如果数组尚不存在,它将向数组添加元素。
如果值是文档,如果数组中的现有文档与要添加的文档完全匹配,则MongoDB确定文档是重复的;即,现有文档具有完全相同的字段和值,并且字段的顺序相同。因此,字段顺序很重要,您无法指定MongoDB仅比较文档中字段的子集来确定文档是否与现有数组元素重复。
由于您尝试添加整个文档,因此您受到此限制。
所以我看到了以下解决方案:
读入数组,查看它是否包含您想要的元素,如果没有,则将其推送到values
push
数组。
这有缺点 NOT 是一个原子操作意味着你最终可能会重复。如果您运行定期清理作业以从每个文档的此字段中删除重复项,则可以接受这一点。
由您决定是否可以接受。
假设您将字段_id
放在values
字段的子文档中,请停止操作。假设mongoose正在为你做这件事(因为根据我的理解,这样做)阻止它像这里所说的那样做:Stop mongoose from creating _id
for subdocument in arrays。
接下来,您需要确保文档中的字段始终具有相同的顺序,因为在比较上述引文中所述的addToSet
操作中的文档时,顺序很重要。
将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年且尚未关闭(我认为这是一种耻辱)。
您可以从这里做的事情是有限的,所有都在应用程序级别。我有同样的问题,并在应用程序级别解决它。做这样的事情:
_id
和values.day
的文档。 push
新值(我假设band_profile_history
已记录{{1 }} value。。_id
运算符使用set
操作。像其他人说的那样,它们不是原子的,但是当你在应用程序级别处理问题时,你可以制作一大堆代码同步。在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}
,因此您可以使用它来计算天数,并仅针对当前日期进行更新。