ang*_*244 4 mongodb node.js mongodb-query
根据文件:
$ pull运算符从现有数组中删除与指定条件匹配的值的所有实例.
是否可以选择仅删除值的第一个实例?例如:
var array = ["bird","tiger","bird","horse"]
Run Code Online (Sandbox Code Playgroud)
如何在更新通话中直接删除第一个"鸟"?
所以你是正确的,因为$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数组中的任何条目.
这就是你如何安全地从数组中删除值的"第一次"出现.要确定该数组是否包含多个相同的值,这是另一个问题.
值得注意的是,虽然这里的另一个答案确实是正确的,但这里的一般方法是使用$unset匹配的数组元素来创建一个null值,然后$pull只null创建数组中的值,但在现代 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 进行修复以允许包含此表达式之前,这将是必需的。
| 归档时间: |
|
| 查看次数: |
1595 次 |
| 最近记录: |