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

ang*_*244 4 mongodb node.js mongodb-query

根据文件:

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

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

var array = ["bird","tiger","bird","horse"]
Run Code Online (Sandbox Code Playgroud)

如何在更新通话中直接删除第一个"鸟"?

Bla*_*ven 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) {

    }
);
Run Code Online (Sandbox Code Playgroud)

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

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

    }
);
Run Code Online (Sandbox Code Playgroud)

但是,如果要删除的"第一个"项目的数组位置未知,则两者都没有多大用处.

对于这里的一般方法,您需要"两个"更新,一个匹配第一个项目并将其替换为唯一要删除的内容,第二个实际删除该修改后的项目.

如果应用简单更新而不是要求返回的文档,这会更简单,也可以跨文档批量完成.它还有助于使用像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   
    }
);
Run Code Online (Sandbox Code Playgroud)

因此,将$unset此处与位置$运算符一起使用可以将"first"项目更改为null.然后后续查询$pull只删除null数组中的任何条目.

这就是你如何安全地从数组中删除值的"第一次"出现.要确定该数组是否包含多个相同的值,这是另一个问题.


Nei*_*unn 5

值得注意的是,虽然这里的另一个答案确实是正确的,但这里的一般方法是使用$unset匹配的数组元素来创建一个null值,然后$pullnull创建数组中的值,但在现代 MongoDB 版本中有更好的方法来实现这一点。

使用bulkWrite()

作为提交两个操作以作为单独的请求按顺序更新的替代情况,现代 MongoDB 版本通过推荐的方法支持批量操作,该方法允许将这些多个更新作为具有单个响应的单个请求提交: bulkWrite()

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

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

使用聚合表达式

随着 MongoDB 4.2 的发布,现在 MongoDB 的各种“更新”操作中允许使用聚合表达式。这是一个单一的管道阶段$addFields$set(这是一个别名,$addFields旨在使这些“更新”语句读起来更符合逻辑),$project或者$replaceRoot它自己的别名$replaceWith。管道$redact阶段在某种程度上也适用于此。基本上任何返回“重塑”文档的管道阶段都是允许的。

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

在这种情况下,使用的操作是实现$sliceand$indexOfArray运算符,以基本上拼凑出一个新数组,该数组“跳过”一个匹配的数组元素。这些片段通过运算符连接起来,返回一个缺少第一个$concatArrays匹配元素的新数组。

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

当然,唯一的问题是 4.2 之前的任何 MongoDB 版本都不支持此功能。另一方面bulkWrite()可能是较新的 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();
  }


})();
Run Code Online (Sandbox Code Playgroud)

和输出:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

注意:示例列表使用mongoose,部分原因是它在给出的其他答案中被引用,部分原因是还通过聚合语法示例演示了一个重要点。请注意,代码使用ArrayTest.collection.updateOne()自 Mongoose 当前版本(撰写本文时为 5.7.1)以来,此类更新的聚合管道语法已被标准 mongoose 模型方法删除。

因此,可以使用访问器从核心 MongoDB Node 驱动程序.collection获取底层对象。Collection在对 mongoose 进行修复以允许包含此表达式之前,这将是必需的。