在upsert上更新时,mongodb $ addToSet到非数组字段

时间:2015-03-20 14:08:48

标签: mongodb

我最近的项目遇到了与此问题相同的问题:the question

db.test.update(
    {name:"abc123", "config.a":1  }, 
    {$addToSet:{ config:{a:1,b:2} } }, 
    true 
)

会产生这样的错误:

  

无法将$ addToSet应用于非数组字段

但改为:

db.test.update(
    {name:"abc123", "config.a":{$in:[1]}  }, 
    {$addToSet:{ config:{a:1,b:2} } }, 
    true 
)
  

工作正常。

还引用了此链接:Answer

任何人都能解释一下发生了什么吗? &#34; config.a&#34;:1 会将 config 变为对象?哪里&#34; config.a&#34;:{$ in:[1]} 赢了<?p?

2 个答案:

答案 0 :(得分:23)

您在此处尝试执行的操作是仅在项目不存在的情况下向数组添加新项目,并创建不存在该项目的新文档。您选择$addToSet是因为您希望这些项目是唯一的,但事实上您确实希望它们是唯一的&#34; a&#34;仅

所以$addToset不会这样做,而你需要&#34;测试&#34;存在的元素。但这里真正的问题是不可能同时做到这一点并且&#34; upsert&#34;同时。逻辑无法工作,因为每当找不到数组元素时都会创建一个新文档,而不是像你想要的那样附加到数组元素。

设计为$addToSet的当前操作错误不能用于&#34;创建&#34;一个数组,但仅限于&#34;添加&#34;成员到现有数组。但如前所述,您在实现逻辑方面还存在其他问题。

这里需要的是一系列更新操作,每个操作都会尝试&#34;执行他们预期的行动。这只能通过多个语句来完成:

// attempt "upsert" where document does not exist
// do not alter the document if this is an update
db.test.update(
    { "name": "abc" },
    { "$setOnInsert": { "config": [{ "a": 1, "b": 2 }] }},
    { "upsert": true }
)

// $push the element where "a": 1 does not exist
db.test.update(
    { "name": "abc", "config.a": { "$ne": 1 } },
    { "$push": { "config": { "a": 1, "b": 2 } }}
)

// $set the element where "a": 1 does exist
db.test.update(
    { "name": "abc", "config.a": 1 },
    { "$set": { "config.$.b": 2 } }
)

在第一次迭代中,第一个语句将&#34; upsert&#34;文档并使用项创建数组。第二个陈述与文件不符,因为&#34; a&#34; element具有指定的值。第三个语句将与文档匹配,但在写操作中不会改变它,因为值没有改变。

如果您现在将输入更改为"b": 3,则会得到不同的响应,但会得到所需的结果:

db.test.update(
    { "name": "abc" },
    { "$setOnInsert": { "config": [{ "a": 1, "b": 3 }] }},
    { "upsert": true }
)

db.test.update(
    { "name": "abc", "config.a": { "$ne": 1 } },
    { "$push": { "config": { "a": 1, "b": 3 } }}
)

db.test.update(
    { "name": "abc", "config.a": 1 },
    { "$set": { "config.$.b": 3 } }
)

所以现在第一个语句将文档与"name": "abc"匹配,但是没有做任何事情,因为唯一有效的操作是&#34;插入&#34;。第二个陈述不匹配,因为&#34; a&#34;符合条件。第三个参数与&#34; a&#34;的值相匹配。和变化&#34; b&#34;在匹配的元素中达到所需的值。

随后改变&#34; a&#34;对于数组中不存在的另一个值,允许1和3都不做任何事情,但第二个语句将另一个成员添加到数组中,保持内容的唯一性为&#34; a&#34;密钥。

同时提交一份不对现有数据进行任何更改的声明,当然会产生一个回复,表明并未对所有帐户进行任何更改。

您的操作方式。您可以使用&#34; ordered&#34; Bulk操作,以便服务器只有一个请求和响应,并且修改或创建了有效响应。

答案 1 :(得分:1)

如本期MongoDB JIRA(https://jira.mongodb.org/browse/SERVER-3946)所述,可以通过单个查询解决该问题:

以下更新失败,因为我们在也包含在第一个参数中的字段(接受查询的字段和值的字段)上使用了$addToSet。据我了解,在这种情况下,我们upsert: true会与我们查询的相同字段一起使用,因此无法使用$addToSet

db.foo.update({x : "a"},  {$addToSet: {x: "b"}} , {upsert: true}); // FAILS

解决方案是使用$elemMatch: {$eq: field: value}

db.foo.update({x: {$elemMatch: {$eq: "a"}}}, {$addToSet: {x: "b"}}, {upsert: true});