用户集合包含一系列网站。每个站点都有一个名称。我希望所有网站名称对每个用户都是唯一的。
user-1有两个名为" a"和" b"和用户2,其网站名为" a"没关系。但是有两个网站的用户1称为" a"和" a"不行。
以下是尝试更新网站的名称,但仅限于每个用户的唯一名称。不幸的是,它不起作用:
collection.update({
_id: userId,
'sites._id' : req.params.id,
'sites' : {$not : {$elemMatch : {name: req.body.name}}}
},
{ $set: { 'sites.$.name': req.body.name } });
数据示例:
db.users.insert({
"_id" : ObjectId("59f1ebf8090f072ee17438f5"),
"email" : "user.email@gmail.com",
"sites" : [
{
"_id" : ObjectId("59f5ebf8190f074ee17438f3"),
"name" : "site 1"
},
{
"_id" : ObjectId("59f5bbf8194f074be17438f2"),
"name" : "site 2"
}
]});
答案 0 :(得分:1)
所以这里的问题是你需要通过数组中的特定_id
值进行更新,但只能在另一个元素中不存在"name"
的情况下进行更新。特别的问题是positional $
operator以及如何使用"匹配的"元件。
点是你想要"位置"来自_id
但整个数组上的另一个约束,而不会实际影响文档匹配或位置。
您无法使用$elemMatch
,因为"name"
测试适用于数组的所有元素,而不管_id
。并且您不能"dot notate"查询中的两个字段,因为匹配条件可以在不同的位置找到。例如,第一个数组元素上的"site 3"
不匹配,但第二个数组元素上可能会匹配提供的_id
值。
目前,positional $
operator仅适用于找到的第一次匹配。所以当前需要发生的是我们只能保持_id
匹配的位置,即使你仍然想要一个约束来寻找" unique" "name"
内的值,在更改之前当前不存在于另一个中。
目前唯一可以保持位置但能够测试数组没有"name"
的方法是使用$where
和一个查看数组的JavaScript表达式。诀窍是普通查询条件与"位置匹配,但$where
影响文档选择而不会影响"位置"匹配。
因此尝试将"site 2"
写入第一个数组条目:
db.users.update(
{
"_id" : ObjectId("59f1ebf8090f072ee17438f5"),
"sites._id": ObjectId("59f5ebf8190f074ee17438f3"),
"$where": '!this.sites.some(s => s.name === "site 2")'
},
{
"$set": { "sites.$.name": "site 2" }
}
)
因为约束条件而无需更新:
!this.sites.some(s => s.name === "site 2")
显示至少有一个数组元素通过Array.some()
实际符合条件,因此文档将被拒绝更新。
当您在第二个元素上更改为"site 3"
时:
db.users.update(
{
"_id" : ObjectId("59f1ebf8090f072ee17438f5"),
"sites._id": ObjectId("59f5bbf8194f074be17438f2"),
"$where": '!this.sites.some(s => s.name === "site 3")'
},
{
"$set": { "sites.$.name": "site 3" }
}
)
$where
子句中没有数组元素匹配,但_id
当然会这样做,因此更新中会使用此位置。因此,更新正确应用于匹配的元素,而不仅仅是"第一个元素"它没有名字:
/* 1 */
{
"_id" : ObjectId("59f1ebf8090f072ee17438f5"),
"email" : "user.email@gmail.com",
"sites" : [
{
"_id" : ObjectId("59f5ebf8190f074ee17438f3"),
"name" : "site 1"
},
{
"_id" : ObjectId("59f5bbf8194f074be17438f2"),
"name" : "site 3"
}
]
}
从MongoDB 3.6我们可以做到这一点,而不是依赖于JavaScript评估作为&#34;解决方法&#34;。新添加的是positional filtered $[<identifier>]
运算符,它允许我们转身&#34;这些条件如上所述,而不是&#34;查询&#34;本地地在"name"
但是&#34;过滤器&#34;在"_id"
值:
db.users.update(
{
"_id": ObjectId("59f1ebf8090f072ee17438f5"),
"sites.name": { "$ne": "site 4" }
},
{ "$set": { "sites.$[el].name": "site 4" } },
{ "arrayFilters": [{ "el._id": ObjectId("59f5bbf8194f074be17438f2") }] }
)
所以这里我们得到了第二个数组元素的正确结果,尽管查询条件实际上满足了&#34;匹配数组的第一个元素。这是因为在&#34;文档选择&#34;之后接管了什么?查询是如何定义arrayFilters
以及位置过滤运算符。对于来自[el]
数组的匹配元素,我们将其别名为"sites"
,并在_id
定义中查找它的arrayFilters
子属性。
这实际上使读取更有意义,并且更容易构造表示条件的JavaScript语句。因此,将其添加到MongoDB进行更新是一个非常有用的好处。
所以现在,使用$where
进行位置匹配的代码仍然会对数组进行约束。从MongoDB 3.6开始,这将更容易编码并且更高效,因为文档排除可以在本机代码运算符中完全完成,同时仍保留匹配位置的方法。