[英]How to pull one instance of an item in an array in MongoDB?
根据文件:
$pull 运算符从现有数组中删除与指定条件匹配的一个或多个值的所有实例。
是否可以选择仅删除值的第一个实例? 例如:
var array = ["bird","tiger","bird","horse"]
如何在更新调用中直接删除第一个“鸟”?
所以您是正确的,因为$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
条目。
这样可以安全地从数组中删除值的“第一个”出现。 确定该数组是否包含多个相同的值是另一个问题。
值得注意的是,尽管这里的另一个答案确实是正确的,但此处的一般方法是: $unset
匹配的数组元素以创建null
值,然后$pull
仅从数组中$pull
null
值,但还有更好的方法在现代的MongoDB版本中实现这一点。
bulkWrite()
作为将两个操作作为单独的请求依次提交以进行更新的另一种情况,现代的MongoDB版本通过建议的 bulkWrite()
方法来支持批量操作 ,该方法允许将多个更新作为单个请求提交给单个响应:
collection.bulkWrite(
[
{ "updateOne": {
"filter": { "array": "bird" },
"update": {
"$unset": { "array.$": "" }
}
}},
{ "updateOne": {
"filter": { "array": null },
"update": {
"$pull": { "array": null }
}
}}
]
);
与答案相同,显示为两个请求,但这次只是一个 。 这样可以节省服务器通信中的大量开销,因此通常是更好的方法。
随着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" }
]}
]
}
}}
]
);
在这种情况下,使用的操作是实现$slice
和$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();
}
})();
并输出:
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
}
}
注意 :示例清单使用的是mongoose ,部分是因为在给出的其他答案中已引用了它,部分是为了演示聚合语法示例的重点。 请注意,该代码使用
ArrayTest.collection.updateOne()
因为在当前版本的Mongoose(撰写本文时为5.7.1)中,通过标准的Mongoose Model方法删除了此类更新的聚合管道语法。这样,可以使用
.collection
访问器,以便从核心MongoDB节点驱动程序获取基础Collection
对象。 直到对猫鼬进行了修复(允许包含此表达式)之前,这是必需的。
如本Jira中所述,此功能将永远不会正确存在。
我推荐使用的方法是通过聚合管道更新语法,如另一个答案中提出的那样,但是该答案有一些失败的边缘情况 - 例如,如果数组中不存在该元素,这是一个适用于所有人的工作版本边缘情况。
ArrayTest.updateOne({},
[
{
"$set": {
"array": {
"$concatArrays": [
{
$cond: [
{
$gt: [
{
"$indexOfArray": [
"$array",
"horse"
]
},
0
]
},
{
"$slice": [
"$array",
0,
{
"$indexOfArray": [
"$array",
"horse"
]
}
]
},
[]
]
},
{
"$slice": [
"$array",
{
"$add": [
{
"$indexOfArray": [
"$array",
"horse"
]
},
1
]
},
{
"$size": "$array"
}
]
}
]
}
}
}
])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.