如何在MongoDB中拉出数组中项目的一个实例?

时间:2015-08-14 21:26:18

标签: node.js mongodb mongodb-query

根据文件:

  

$ pull运算符从现有数组中删除与指定条件匹配的值的所有实例。

是否可以选择仅删除值的第一个实例?例如:

var array = ["bird","tiger","bird","horse"]

第一只"鸟怎么样?在更新通话中直接删除?

2 个答案:

答案 0 :(得分:5)

所以你是正确的,因为$pull运算符完全符合文档的说法,因为它的参数实际上是一个"查询"用于匹配要删除的元素。

如果你的数组内容碰巧总是在"第一个"您显示的位置然后$pop运算符实际上删除了第一个元素。

使用基本节点驱动程序:

collection.findOneAndUpdate(
    { "array.0": "bird" },       // "array.0" is matching the value of the "first" element 
    { "$pop": { "array": -1 } },
    { "returnOriginal": false },
    function(err,doc) {

    }
);

使用mongoose返回修改后的文档的参数是不同的:

MyModel.findOneAndUpdate(
    { "array.0": "bird" },
    { "$pop": { "array": -1 } },
    { "new": true },
    function(err,doc) {

    }
);

但是,如果"第一个"的数组位置都不是很有用。要删除的项目未知。

对于这里的一般方法,你需要"两个"更新,是一个匹配第一个项目并将其替换为唯一要删除的东西,第二个实际删除该修改的项目。

如果应用简单的更新而不是要求返回的文档,这会更简单,也可以在文档中批量完成。它还有助于使用async.series之类的内容,以避免嵌套您的呼叫:

async.series(
    [
        function(callback) {
            collection.update(
                { "array": "bird" },
                { "$unset": { "array.$": "" } },
                { "multi": true }
                callback
            );
        },
       function(callback) {
           collection.update(
                { "array": null },
                { "$pull": { "array": null } },
                { "multi": true }
                callback
           );
       }
    ],
    function(err) {
       // comes here when finished or on error   
    }
);

因此,使用$unset运算符positional $允许"首先"要更改为null的项目。然后使用$pull的后续查询只删除数组中的任何null条目。

这就是你删除"第一个"从数组安全地发生值。要确定该数组是否包含多个相同的值,这是另一个问题。

答案 1 :(得分:0)

值得注意的是,尽管这里的另一个答案确实是正确的,但此处的一般方法是$unset匹配的数组元素,以便创建null值,然后创建$pull只是数组中的null值,还有更好的方法可以在现代MongoDB版本中实现。

使用bulkWrite()

作为提交两个单独的请求按顺序更新的 操作的另一种情况,现代的MongoDB版本通过 recommended { {3}}方法,该方法允许将这些多个更新作为带有单个响应的单个请求提交:

    collection.bulkWrite(
      [
        { "updateOne": {
          "filter": { "array": "bird" },
          "update": {
            "$unset": { "array.$": "" }
          }
        }},
        { "updateOne": {
          "filter": { "array": null },
          "update": {
            "$pull": { "array": null }
          }
        }}
      ]
    );

与答案相同,显示为两个请求,但这次仅为一个。这样可以节省服务器通信中的大量开销,因此通常是更好的方法。

使用聚合表达式

随着MongoDB 4.2的发布,现在可以在MongoDB的各种“更新”操作中使用bulkWrite()。这是aggregation expressions$addFields(这是$addFields的别名,意在使这些“更新”语句更逻辑地读取),$set或{ {3}}和它自己的别名$project$replaceRoot流水线阶段在某种程度上也适用于此。基本上,任何返回“ reshaped” 文档的管道阶段都是允许的。

collection.updateOne(
  { "array": "horse" },
  [
    { "$set": {
      "array": {
        "$concatArrays": [
          { "$slice": [ "$array", 0, { "$indexOfArray": [ "$array", "horse" ] }] },
          { "$slice": [
            "$array",
            { "$add": [{ "$indexOfArray": [ "$array", "horse" ] }, 1] },
            { "$size": "$array" }
          ]}
        ]
      }
    }}
  ]
);

在这种情况下,操纵用于实现$replaceWith$redact运算符,以本质上将它们拼凑成一个新数组,其中在匹配的第一个元素上“跳过” 。这些片段通过$slice运算符进行 join ,并返回 first 匹配元素不存在的新数组。

这现在可能更有效,因为仍然是单个请求的操作现在也是单个操作,并且会减少服务器 em>开销。

当然,唯一的问题是4.2之前的任何MongoDB版本均不支持此功能。另一方面,$indexOfArray可能是较新的API实现,但是对服务器的实际基础调用将应用回MongoDB 2.6,从而实现实际的“批量API”调用,甚至通过所有方式回归到较早版本核心驱动程序实际上实现了此方法。

演示

作为演示,这是两种方法的清单:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/test';
const opts = { useNewUrlParser: true, useUnifiedTopology: true };

mongoose.Promise = global.Promise;

mongoose.set('debug', true);
mongoose.set('useCreateIndex', true);
mongoose.set('useFindAndModify', false);


const arrayTestSchema = new Schema({
  array: [String]
});

const ArrayTest = mongoose.model('ArrayTest', arrayTestSchema);

const array = ["bird", "tiger", "horse", "bird", "horse"];

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {
    const conn = await mongoose.connect(uri, opts);

    await Promise.all(
      Object.values(conn.models).map(m => m.deleteMany())
    );

    await ArrayTest.create({ array });

    // Use bulkWrite update
    await ArrayTest.bulkWrite(
      [
        { "updateOne": {
          "filter": { "array": "bird" },
          "update": {
            "$unset": { "array.$": "" }
          }
        }},
        { "updateOne": {
          "filter": { "array": null },
          "update": {
            "$pull": { "array": null }
          }
        }}
      ]
    );

    log({ bulkWriteResult: (await ArrayTest.findOne()) });

    // Use agggregation expression
    await ArrayTest.collection.updateOne(
      { "array": "horse" },
      [
        { "$set": {
          "array": {
            "$concatArrays": [
              { "$slice": [ "$array", 0, { "$indexOfArray": [ "$array", "horse" ] }] },
              { "$slice": [
                "$array",
                { "$add": [{ "$indexOfArray": [ "$array", "horse" ] }, 1] },
                { "$size": "$array" }
              ]}
            ]
          }
        }}
      ]
    );

    log({ aggregateWriteResult: (await ArrayTest.findOne()) });

  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})();

输出:

Mongoose: arraytests.deleteMany({}, {})
Mongoose: arraytests.insertOne({ array: [ 'bird', 'tiger', 'horse', 'bird', 'horse' ], _id: ObjectId("5d8f509114b61a30519e81ab"), __v: 0 }, { session: null })
Mongoose: arraytests.bulkWrite([ { updateOne: { filter: { array: 'bird' }, update: { '$unset': { 'array.$': '' } } } }, { updateOne: { filter: { array: null }, update: { '$pull': { array: null } } } } ], {})
Mongoose: arraytests.findOne({}, { projection: {} })
{
  "bulkWriteResult": {
    "array": [
      "tiger",
      "horse",
      "bird",
      "horse"
    ],
    "_id": "5d8f509114b61a30519e81ab",
    "__v": 0
  }
}
Mongoose: arraytests.updateOne({ array: 'horse' }, [ { '$set': { array: { '$concatArrays': [ { '$slice': [ '$array', 0, { '$indexOfArray': [ '$array', 'horse' ] } ] }, { '$slice': [ '$array', { '$add': [ { '$indexOfArray': [ '$array', 'horse' ] }, 1 ] }, { '$size': '$array' } ] } ] } } } ])
Mongoose: arraytests.findOne({}, { projection: {} })
{
  "aggregateWriteResult": {
    "array": [
      "tiger",
      "bird",
      "horse"
    ],
    "_id": "5d8f509114b61a30519e81ab",
    "__v": 0
  }
}
  

注意:示例清单使用的是$concatArrays,部分原因是在另一个给出的答案中引用了该清单,部分原因还在于展示了 aggregate 的重要意义>语法示例。请注意,该代码使用ArrayTest.collection.updateOne(),因为在当前版本的Mongoose(撰写本文时为5.7.1)中,聚集流水线语法已被删除标准的猫鼬模型方法。

     

因此,可以使用.collection访问器来从核心MongoDB节点驱动程序获取基础Collection对象。在对猫鼬进行修复之前,必须先执行此操作,以允许包含此表达式。