我有一个包含数组的文档,如下所示:
"userTags" : [
"foo",
"foo",
"foo",
"foo",
"moo",
"bar"
]
如果我执行db.products.update({criteriaToGetDocument}, {$push: {userTags: "foo"}}}
,我可以正确地将foo
的另一个实例插入到数组中。
但是,如果我执行db.products.update({criteriaToGetDocument}, {$pull: {userTags: "foo"}}}
,那么它会从数组中删除 foo
的所有实例,并留下:
"userTags" : [
"moo",
"bar"
]
这根本不会做,我只想从数组中拉出一个项而不是全部。如何更改命令以便只删除一个foo
?是否有某种$pullOnce
方法可以在这里工作?
答案 0 :(得分:4)
不,目前没有这样的事情。很多人已经请求了该功能,您可以在mongodb Jira中跟踪它。至于你可以看到它没有得到解决也没有安排(这意味着你在不久的将来没有运气)。
唯一的选择是使用应用程序逻辑来实现这一目标:
请记住,此操作会破坏原子性,但由于Mongo没有提供本机方法,因此您将以任何方式破坏原子性。
我为新答案移动了一个替代解决方案,因为它没有回答这个问题,而是代表重构现有架构的方法之一。它也变得如此之大,开始比原来的答案大得多。
答案 1 :(得分:2)
如果您确实想要使用此功能,则必须考虑修改架构。一种方法是做这样的事情:
"userTags" : {
"foo" : 4,
"moo": 1,
"bar": 1
}
数字是标签的数量。这样您就可以使用atomic $ inc来添加或删除标记。虽然添加新标签可能是合适的,但删除标签时必须要小心。你必须检查标签是否存在,并且它是> =然后是1。
以下是如何做到这一点:
db.a.insert({
_id : 1,
"userTags" : {
"foo" : 4,
"moo": 1,
"bar": 1
}
})
要添加一个标记,您需要执行此操作:
db.a.update({_id : 1}, {$inc : {'userTags.zoo' : 1}})
如果标签在创建之前不存在
要删除标记,您需要多做一点:
db.a.update({
_id : 1,
'userTags.moo' : {$gte : 1 }
}, {
$inc : {'userTags.moo' : -1}
})
在这里你要检查元素是否存在并且大于1(不需要检查是否存在,因为$ gte也是这样做的。)
请记住,这种方法有一个缺点。您可以将某些标记列为字段,但不是真正的标记(它们的值为0)。因此,如果你想找到带有标签foo的所有元素,你必须记住这一点并做类似的事情:
db.a.find({'userTags.zoo' : { $gte : 1}})
此外,当您输出元素的所有标记时,您可能会遇到同样的问题。因此,您需要清理应用程序层上的输出。
答案 2 :(得分:0)
我知道这是一种解决方法而不是真正的解决方案但是,当我遇到同样的问题时,我发现至少在PHP中,你可以做到这样的事情:
// Remove up to two elements "bar" from an array called "foo"
// in a document that matches the query {foo:"bar"}
$un_bar_qty = 2;
$un_bar_me = $db->foo->findOne(array("foo"=>"bar"));
foreach($un_bar_me['bar'] as $foo => $bar) {
if($bar == 'bar') {
unset($un_bar_me['bar'][$foo]);
$un_bar_qty--;
}
if(!$un_bar_qty) break;
}
$db->foo->update(array('_id'=>$un_bar_me['_id']),$bar);
之前的文件:
{
{ foo: "bar" },
{ bar: [ "bar" , "bar" , "foo", "bar" ] }
}
文件后:
{
{ foo: "bar" },
{ baz: [ "foo", "bar" ] }
}
做到这一点并不太糟糕。对于我而言,对于我来说,似乎没有麻烦,只需要弄乱标签和增量,YMMV。