在MongoDB中,如何通过索引删除数组元素?

112

在以下示例中,假设文档位于 db.people 集合中。

如何通过其 索引 删除 interests 数组的第三个元素?

{
  "_id" : ObjectId("4d1cb5de451600000000497a"),           
  "name" : "dannie",  
  "interests" : [  
    "guitar",  
    "programming",           
    "gadgets",  
    "reading"  
  ]   
}

这是我的当前解决方案:

var interests = db.people.findOne({"name":"dannie"}).interests;  
interests.splice(2,1)  
db.people.update({"name":"dannie"}, {"$set" : {"interests" : interests}});

有没有更直接的方式?


https://docs.mongodb.org/manual/reference/operator/update/pull/ - userMod2
9个回答

156

没有直接通过数组索引进行删除的方法。实际上,这是一个未解决的问题 http://jira.mongodb.org/browse/SERVER-1014 ,你可以为它投票。

解决方法是使用 $unset,然后再使用 $pull:

db.lists.update({}, {$unset : {"interests.3" : 1 }}) 
db.lists.update({}, {$pull : {"interests" : null}})

更新:如评论中所提到的,这种方法不是原子性的,如果其他客户端在两个操作之间读取和/或写入,可能会导致某些竞争条件。如果我们需要操作具有原子性,我们可以:

  • 从数据库中读取文档
  • 更新文档并从数组中删除该项
  • 替换数据库中的文档。为确保文档自我们读取它以来未发生更改,我们可以使用Mongo文档中描述的当前更新模式isolate-sequence-of-operations

38
这已经一年半了?Mongodb的情况仍然如此吗? - Abe
2
@Javier - 如果在计算索引位置和执行取消设置之间数据库发生更改,那么这种解决方案会让你面临问题,不是吗? - UpTheCreek
2
这不是原子操作,如果您的代码没有准备好处理数组中的空条目,则很可能会导致难以理解的竞争条件。 - Glenn Maynard
@UpTheCreek 你说得对。如果你需要确保文档没有被更改,你可以使用这里描述的“隔离操作序列”的更新方法:http://docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations/。 - Javier Ferrero
4
八年过去了,这个工单仍未被执行... - Olly John
显示剩余2条评论

19
您可以使用update操作的$pull修改器来删除数组中的特定元素。如果提供了查询条件,它将如下所示:
db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})

此外,您可以考虑使用$pullAll来移除所有出现的元素。更多信息请参见官方文档页面 - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

这种方式不会使用索引作为删除元素的准则,但仍可能在类似情况下有所帮助。在我所知道的范围内,对于在数组中定位元素,使用索引并不是非常可靠,因为MongoDB在元素顺序上不一致。


6
他的元素是一个数组。在这种情况下Mongo中的顺序是一致的。事实上,所有的文档都是有序集合,但是许多驱动程序不以这种方式处理它们。进一步复杂化问题的是,如果一个文档在更新操作之后需要移动(由于增长超出填充因子),键的顺序会突然变成字母顺序(在底层,我认为它们按照二进制值进行排序,这种怀疑是基于我的知识,即数组几乎是具有连续整数键而不是字符串的标准文档)。 - marr75
这样可以避免未设置+提取的问题,但需要确保字典是严格有序的。大多数语言中的字典是无序的,因此实现这一点可能会很麻烦。 - Glenn Maynard
@marr75:你有参考资料吗?Mongo文档始终应该保留顺序,如果它重新排序子文档,很多东西都会出问题。 - Glenn Maynard
我没有参考资料。我是通过在2.2版本中修复由更新和移动文档引入的错误而亲身体验得知的。过去几个月我没有做过太多的Mongo工作,所以我无法对更当前的版本发表意见。 - marr75
就元素一致性而言,我的期望用例是这样的。客户端从Mongo请求JSON,然后如果客户端选择删除JSON数组中的某个项目,则会将该数组索引传递给服务器进行删除。如果数组项的位置发生了变化,那么这可能会很奇怪,但这可以解释为什么他们无法实现该功能。 - Northstrider
数组始终会保持它们的“顺序”,因为它们基本上是哈希表,其中键是给定的索引。这就是 JSON 的工作原理,因为这(几乎)就是 JS 的工作原理。["zero", "one", "two"]类似于{"0":"zero", "1":"one", "2":"two"},而这些键转换为数组顺序。 - Jimbo Jonny

12

Mongodb 4.2中可以这样做:

db.example.update({}, [
     {$set: {field: {
           $concatArrays: [ 
                  {$slice: ["$field", P]}, 
                  {$slice: ["$field", {$add: [1, P]}, {$size: "$field"}]}
           ]
     }}}
]);

P是您想从数组中删除的元素的索引。

如果您希望从P开始一直到最后删除:

db.example.update({}, [
  { $set: { field: { $slice: ["$field", 1] } } },
]);

需要注意的是,这是MongoDB开发人员在决定不直接实现它时推荐的解决方案(请参见)。 - tanius

8

从Mongo 4.4开始,$function聚合操作符允许应用自定义的JavaScript函数来实现MongoDB查询语言不支持的行为。

例如,若要通过删除给定索引处的元素来更新数组:

// { "name": "dannie", "interests": ["guitar", "programming", "gadgets", "reading"] }
db.collection.update(
  { "name": "dannie" },
  [{ $set:
    { "interests":
      { $function: {
          body: function(interests) { interests.splice(2, 1); return interests; },
          args: ["$interests"],
          lang: "js"
      }}
    }
  }]
)
// { "name": "dannie", "interests": ["guitar", "programming", "reading"] }

$function 接受 3 个参数:

  • body,这是要应用的函数,其参数是要修改的数组。此处的函数只是简单地使用 splice 方法删除索引为 2 的元素。
  • args,其中包含记录中 body 函数所需的字段。在我们的情况下为 "$interests"
  • lang,这是 body 函数编写的语言。目前仅支持 js

4

与其使用unset(如接受的答案中所述),我通过将字段设置为唯一值(即不是NULL),然后立即提取该值来解决此问题。从异步角度来看,更加安全。以下是代码:

    var update = {};
    var key = "ToBePulled_"+ new Date().toString();
    update['feedback.'+index] = key;
    Venues.update(venueId, {$set: update});
    return Venues.update(venueId, {$pull: {feedback: key}});

希望Mongo能解决这个问题,可能会通过扩展$position修饰符来支持$pull和$push。

2
我建议使用GUID(我倾向于使用ObjectID)字段或自增字段来为数组中的每个子文档标识。使用GUID可以轻松地执行$pull操作,并确保正确的文档被删除。其他数组操作也是如此。

1
对于正在使用nodejs的mongoose寻找答案的人,这就是我做的方式。
exports.deletePregunta = function (req, res) {
let codTest = req.params.tCodigo;
let indexPregunta = req.body.pregunta; // the index that come from frontend
let inPregunta = `tPreguntas.0.pregunta.${indexPregunta}`; // my field in my db
let inOpciones = `tPreguntas.0.opciones.${indexPregunta}`; // my other field in my db
let inTipo = `tPreguntas.0.tipo.${indexPregunta}`; // my  other field in my db

Test.findOneAndUpdate({ tCodigo: codTest },
    {
        '$unset': {
            [inPregunta]: 1, // put the field with [] 
            [inOpciones]: 1,
            [inTipo]: 1
        }
    }).then(()=>{ 
    Test.findOneAndUpdate({ tCodigo: codTest }, {
        '$pull': {
            'tPreguntas.0.pregunta': null,
            'tPreguntas.0.opciones': null,
            'tPreguntas.0.tipo': null
        }
    }).then(testModificado => {
        if (!testModificado) {
            res.status(404).send({ accion: 'deletePregunta', message: 'No se ha podido borrar esa pregunta ' });
        } else {
            res.status(200).send({ accion: 'deletePregunta', message: 'Pregunta borrada correctamente' });
        }
    })}).catch(err => { res.status(500).send({ accion: 'deletePregunta', message: 'error en la base de datos ' + err }); });
 }

如果您对这个答案理解不够好,我可以重写它,但我认为现在没问题了。

希望这可以帮助您,我在解决这个问题上花费了很多时间。


-1

这可以使用 $pop 操作符完成,
db.getCollection('collection_name').updateOne( {}, {$pop: {"path_to_array_object":1}})


1
$pop运算符仅移除数组的第一个或最后一个元素。 - floreapaun

-1

虽然有点晚了,但是对于那些使用robo3t的人来说可能会很有用-

db.getCollection('people').update( 
  {"name":"dannie"}, 
  { $pull: 
   {
       interests: "guitar" // you can change value to 
   } 
  }, 
  { multi: true }
);

如果您有类似于以下的值-

property: [ 
    {
        "key" : "key1",
        "value" : "value 1"
    }, 
    {
        "key" : "key2",
        "value" : "value 2"
    }, 
    {
        "key" : "key3",
        "value" : "value 3"
    }
]

如果您想删除一个键为 key3 的记录,那么可以使用以下代码 -
 db.getCollection('people').update( 
  {"name":"dannie"}, 
  { $pull: 
   {
       property: { key: "key3"} // you can change value to 
   } 
  }, 
  { multi: true }
);

同样适用于嵌套属性。

这个问题与“索引”有关! - Siyavash Hamdi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接