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


97

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

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

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

有没有更直接的方法?


Answers:


138

没有直接的方法可以通过数组索引来拉/删除。实际上,这是一个公开问题http://jira.mongodb.org/browse/SERVER-1014,您可以投票赞成。

解决方法是先使用$ unset再使用$ pull:

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

更新:如某些评论中所述,这种方法不是原子的,如果其他客户端在两个操作之间进行读取和/或写入,则可能导致某些竞争状况。如果我们需要操作是原子的,我们可以:

  • 从数据库中读取文档
  • 更新文档并删除数组中的项目
  • 替换数据库中的文档。为了确保文档自阅读以来没有更改,我们可以使用mongo docs中描述的当前模式进行更新

33
已经一年半了?这仍然是mongodb的状态吗?
安倍

下一个答案是更直接的,并且为我工作。尽管不是按索引删除,而是按值删除。

1
@Javier-如果数据库在您计算索引中的位置的时间与进行未设置的时间之间发生了变化,那么这种解决方案是否会让您面临问题?
UpTheCreek 2013年

这不是原子的,因此如果您有任何代码不准备应付数组中的空条目,则很可能导致模糊的竞争条件。
Glenn Maynard

3
8年了,这张票还没有付诸实施……
奥利·约翰

19

您可以使用操作$pull修饰符update删除数组中的特定元素。如果您提供的查询如下所示:

db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})

另外,您可以考虑使用$pullAll来删除所有出现的事件。有关更多信息,请访问官方文档页面-http: //www.mongodb.org/display/DOCS/Updating#Updating-%24pull

这并不使用索引作为删除元素的标准,但在与您类似的情况下仍可能会有所帮助。IMO,使用索引对数组中的元素进行寻址不是很可靠,因为据我所知,mongodb在元素顺序上并不一致。


6
他的要素是问题中的一个数组。在这种情况下,Mongo的订单保持一致。实际上,所有文档实际上都是有序集合,但是许多驱动程序并不以这种方式处理它们。如果使文档在更新操作之后必须移动(由于超出填充因子而增长),则问题进一步复杂化,则按键的顺序突然变成字母顺序(在幕后,我认为它们是按二进制值排序的)基于我的知识,数组几乎是标准文档,其键是连续的整数而不是字符串。
2012年

这避免了unset + pull的问题,但是要求字典必须严格排序。大多数语言中的字典都是无序的,因此很难做到这一点。
Glenn Maynard

@ marr75:您有参考吗?Mongo文档始终应该保留顺序,如果对子文档重新排序,则很多事情都会中断。
格伦·梅纳德

我没有参考。我从2.2的个人经验中了解到这一点,这些错误具有由更新和移动的文档引入的固定错误。在过去的几个月中,我没有做过很多mongo工作,因此我无法再使用最新版本。
marr75

至于元素的一致性,我想要的用例是。客户端从mongo请求json,然后如果客户端选择说从json数组中“删除”某项,则它将数组索引传递给要删除的服务器。如果数组项的位置移动了,那将很奇怪,但可以解释为什么它们
不能实现

4

我不使用未设置(如在接受的答案中那样),而是通过将字段设置为唯一值(即非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来解决。


3

我建议对阵列中的每个子文档使用GUID(我倾向于使用ObjectID)字段或自动递增字段。

使用此GUID,很容易发出$ pull,并确保将正确的一个拉出。其他阵列操作也是如此。


2

从开始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,这是要应用的函数,其参数是要修改的数组。这里的功能仅在于使用拼接删除索引2处的1个元素。
  • args,其中包含该body函数作为参数的记录中的字段。就我们而言"$interests"
  • lang,这body是编写函数的语言。js目前仅可用。

2

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

0

对于使用mongoose和nodejs搜索答案的人。这就是我的方法。

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

如果不太了解,我可以重写此答案,但我认为可以。

希望对您有所帮助,我在此问题上浪费了很多时间。


-3

除了使用$ pull之外,我们还可以使用$ pop通过其索引删除数组中的元素。但是您应该从索引位置减去1以便根据索引删除。

例如,如果要删除索引0中的元素,则应使用-1,对于索引1,则应使用0,以此类推...

查询删除第三个元素(小工具):

db.people.update({"name":"dannie"}, {'$pop': {"interests": 1}})

供参考:https : //docs.mongodb.com/manual/reference/operator/update/pop/


3
根据链接的文档,$pop只能从数组中删除第一个或最后一个元素。此答案中的查询不会删除第三个元素。
特拉维斯·G
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.