我有一个看起来像这样的文档:
{
field: 'value',
field2: 'value',
scan: [
[
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
],
[
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
],
[
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
{
dontDeleteMe: 'keepMe',
arrayToDelete: [0,1,2]
},
],
]
}
我们只想删除嵌套在“扫描”列表列表中的字典中的arrayToDelete实例。
目前,我一直在使用
update({}, {$unset: {"scans.0.0.arrayToDelete":1}}, {multi: true})
删除数组。但是,这只会删除您想像的第一个
(0.0)
。是否可以遍历扫描数组和嵌套数组以删除“ arrayToDelete”,并保留所有其他字段?
谢谢
最佳答案
所以我在评论中问了一个问题,但您似乎已经走开了,所以我想我只是回答了我看到的三种可能的情况。
首先,我不确定嵌套数组中显示的元素是否是数组中的唯一元素,或者实际上arrayToDelete
是这些元素中存在的唯一字段。因此,基本上我需要抽象一点并包括这种情况:
{
field: 'value',
field2: 'value',
scan: [
[
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{ somethingToKeep: 1 },
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
],
[
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{ somethingToKeep: 1 },
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
],
[
{ somethingToKeep: 1 },
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
{
arrayToDelete: [0,1,2],
anotherField: "a"
},
],
]
}
情况1-删除存在该字段的内部数组元素
这将使用
$pull
运算符,因为这将完全删除数组元素。您可以在现代MongoDB中使用以下语句执行此操作:db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{
"$pull": {
"scan.$[a]": { "arrayToDelete": { "$exists": true } }
}
},
{ "arrayFilters": [
{ "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
]
}
)
这样会更改所有匹配的文档,如下所示:
{
"_id" : ObjectId("5ca1c36d9e31550a618011e2"),
"field" : "value",
"field2" : "value",
"scan" : [
[
{
"somethingToKeep" : 1
}
],
[
{
"somethingToKeep" : 1
}
],
[
{
"somethingToKeep" : 1
}
]
]
}
因此,现在删除包含该字段的每个元素。
情况2-只需从内部元素中删除匹配的字段
在这里使用
$unset
。它与您正在执行的“硬索引”版本略有不同:db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{ "$unset": { "scan.$[].$[].arrayToDelete": "" } }
)
将所有匹配的文档更改为:
{
"_id" : ObjectId("5ca1c4c49e31550a618011e3"),
"field" : "value",
"field2" : "value",
"scan" : [
[
{
"anotherField" : "a"
},
{
"somethingToKeep" : 1
},
{
"anotherField" : "a"
},
{
"anotherField" : "a"
},
{
"anotherField" : "a"
}
],
[
{
"anotherField" : "a"
},
{
"anotherField" : "a"
},
{
"anotherField" : "a"
},
{
"somethingToKeep" : 1
},
{
"anotherField" : "a"
}
],
[
{
"somethingToKeep" : 1
},
{
"anotherField" : "a"
},
{
"anotherField" : "a"
},
{
"anotherField" : "a"
},
{
"anotherField" : "a"
}
]
]
}
因此,所有内容仍然存在,但是仅从每个内部数组文档中删除了已标识的字段。
情况3-您实际上想删除阵列中的“所有内容”。
实际上,这只是使用
$set
并清除之前所有内容的简单情况:db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{ "$set": { "scan": [] } }
)
结果应该很好的地方:
{
"_id" : ObjectId("5ca1c5c59e31550a618011e4"),
"field" : "value",
"field2" : "value",
"scan" : [ ]
}
那么这些都在做什么呢?
您应该首先看到的是查询谓词。通常,这是一个很好的主意,以确保您不匹配甚至“尝试”满足文档上甚至不包含要更新的模式的数据的更新条件。嵌套数组充其量是困难的,在实际操作中,您应该避免使用它们,因为您通常“真正的意思”实际上是用单数数组表示的,而附加属性则表示您“认为”嵌套实际上是在为您做的事情。
但是,仅仅因为他们努力并不意味着没有可能。只是您需要了解
$elemMatch
:db.colelction.find(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
}}
)
这是基本的
find()
示例,该示例基于$elemMatch
条件进行匹配,外部数组使用另一个$elemMatch
来匹配内部数组中的另一个条件。即使这个“似乎”是一个奇异的谓词。就像是:"scan.arrayToDelete": { "$exists": true }
就是行不通。都不会:
"scan..arrayToDelete": { "$exists": true }
使用“双点”
..
,因为这基本上是无效的。这是查询谓词,用于匹配需要处理的“文档”,而其余谓词实际上适用于“确定要更新的部分”。
在案例1中,为了从内部数组
$pull
出发,我们首先需要能够识别外部数组的哪些元素包含要更新的数据。这就是"scan.$[a]"
使用positional filtered $[<identifier>]
运算符所做的事情。该运算符基本上将数组中匹配的索引(其中的很多索引)转置到另一个谓词,该谓词在
update
样式命令的第三部分和arrayFilters
部分中定义。本部分基本上从命名标识符的角度定义要满足的条件。在这种情况下,“标识符”被命名为
a
,这是arrayFilters
条目中使用的前缀: { "arrayFilters": [
{ "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } }
]
}
在上下文中与实际的更新语句部分:
{
"$pull": {
"scan.$[a]": { "arrayToDelete": { "$exists": true } }
}
},
然后从
"a"
作为外部数组元素的标识符(首先从"scan"
向内)的角度来看,则条件与原始查询谓词相同,但从“ <内部”第一个“ aa”语句开始。因此,从已经“查看内部”每个外部元素的内容的角度来看,您基本上可以将其视为“查询中的查询”。同样,
$elemMatch
的行为与“查询中的查询”非常相似,因为它自己的参数也从数组元素的角度应用。因此,仅存在$pull
字段,而不是: // This would be wrong! and do nothing :(
{
"$pull": {
"scan.$[a]": { "$elemMatch": { "arrayToDelete": { "$exists": true } } }
}
}
但这都是
arrayToDelete
所特有的,其他情况也有不同的情况:案例2考察了您想在何处仅
$pull
命名字段。您只需为字段命名就很容易了,对吧?完全不正确,因为以下内容显然不符合我们先前所知: { "$unset": { "scan.arrayToDelete": "" } } // Not right :(
当然,注意所有内容的数组索引只是一个痛苦:
{ "$unset": {
"scan.0.0.arrayToDelete": "",
"scan.0.1.arrayToDelete": "",
"scan.0.2.arrayToDelete": "",
"scan.0.3.arrayToDelete": "", // My fingers are tired :-<
} }
这是
$unset
运算符的原因。这个变量比positional all $[]
更具“蛮力”,因为它不匹配$[<identifier>]
中提供的另一个谓词,而是简单地应用于该“索引”处数组内容中的所有内容。实际上,这实际上是说“所有索引”而不用像上面显示的可怕案例那样键入每个单独索引的一种方式。因此,并非所有情况都如此,但它肯定非常适合positional filtered
arrayFilters
,因为它具有非常特定的路径命名,如果该路径与数组的每个元素都不匹配,则当然没有关系。您仍然可以使用
$unset
和arrayFilters
,但是在这里会显得过分杀伤力。另外,演示另一种方法也无害。但是,当然值得理解该语句的外观,所以:
db.collection.updateMany(
{ "scan": {
"$elemMatch": {
"$elemMatch": {
"arrayToDelete": { "$exists": true }
}
}
} },
{ "$unset": { "scan.$[a].$[b].arrayToDelete": "" } },
{
"arrayFilters": [
{ "a": { "$elemMatch": { "arrayToDelete": { "$exists": true } } } },
{ "b.arrayToDelete": { "$exists": true } },
]
}
)
注意到
$[<identifier>]
最初可能不是您期望的,但是考虑到"b.arrayToDelete"
中的位置,从"scan.$[a].$[b]
确实应该有意义,因为元素名称将通过“点符号”到达,如图所示。实际上,在两种情况下。再一次,positional filtered b
无论如何将仅应用于命名字段,因此确实不需要选择标准。还有案例3。这很简单,如果您在删除此内容后不需要在数组中保留其他任何内容(即
$unset
,其中与此匹配的字段是其中唯一的内容,或者$pull
用于),那么根本就不会弄乱其他任何东西,而只需擦除数组即可。如果您认为要澄清具有命名字段的文档是否是嵌套数组中唯一的元素,并且确实有命名键是文档中唯一的内容,则这是一个重要的区别。
理由是,使用此处所示的
$unset
并在这些条件下,您将获得:{
"_id" : ObjectId("5ca321909e31550a618011e6"),
"field" : "value",
"field2" : "value",
"scan" : [
[ ],
[ ],
[ ]
]
}
或使用
$pull
:{
"_id" : ObjectId("5ca322bc9e31550a618011e7"),
"field" : "value",
"field2" : "value",
"scan" : [
[{ }, { }, { }, { }],
[{ }, { }, { }, { }],
[{ }, { }, { }, { }]
]
}
两者显然都不可取。因此,如果
$unset
字段是其中唯一的内容,这就是您的理由,那么删除所有内容的最合乎逻辑的方法就是简单地将数组替换为空数组。或者实际上是arrayToDelete
整个文档属性。但是请注意,所有这些“奇特的东西”(当然是
$unset
除外)都要求您至少必须具有MongoDB 3.6才能使用此功能。如果您仍在运行比该版本更旧的MongoDB(从撰写之日起,您实际上不应该这样做,因为从该日期起仅5个月您的官方支持就用光了),那么
$set
上的其他现有答案是实际上是给你的。
关于mongodb - 删除在任何mongodb数组中找到的字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55449584/