为阵列中的一个字段更新唯一

时间:2017-11-04 08:16:33

标签: mongodb mongodb-query

用户集合包含一系列网站。每个站点都有一个名称。我希望所有网站名称对每个用户都是唯一的。

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

1 个答案:

答案 0 :(得分:1)

所以这里的问题是你需要通过数组中的特定_id值进行更新,但只能在另一个元素中不存在"name"的情况下进行更新。特别的问题是positional $ operator以及如何使用"匹配的"元件。

点是你想要"位置"来自_id但整个数组上的另一个约束,而不会实际影响文档匹配或位置。

您无法使用$elemMatch,因为"name"测试适用于数组的所有元素,而不管_id。并且您不能"dot notate"查询中的两个字段,因为匹配条件可以在不同的位置找到。例如,第一个数组元素上的"site 3"不匹配,但第二个数组元素上可能会匹配提供的_id值。

目前,positional $ operator仅适用于找到的第一次匹配。所以当前需要发生的是我们只能保持_id匹配的位置,即使你仍然想要一个约束来寻找" unique" "name"内的值,在更改之前当前不存在于另一个中。

当前的MongoDB

目前唯一可以保持位置但能够测试数组没有"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

从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开始,这将更容易编码并且更高效,因为文档排除可以在本机代码运算符中完全完成,同时仍保留匹配位置的方法。