聚合管道中过滤数组为空时保留主文档
Keep the main document when filtered array is empty in aggregation pipeline
这真是一个的补充。在@JohnnyHK 的帮助下,我现在可以根据特定条件从数组中删除不需要的子文档:deleted != null
。我发现当数组中没有项目时我的 $unwind
管道坏了,所以我还实现了一个 $cond
来添加一个数组为空的虚拟对象。这是到目前为止的代码:
Collection.aggregate([
{ $match:
{ _id: ObjectID(collection_id) }
},
{
$project:
{
_id: 1,
name: 1,
images: {
$cond:
[
{
$eq:
[
"$images",
[]
]
},
[
dummyImg // Variable containing dummy object
],
'$images'
]
},
}
},
{ $unwind: "$images" },
{ $match:
{ "images.deleted": null }
},
// Regroup the docs by _id to reassemble the images array
{$group: {
_id: '$_id',
name: {$first: '$name'},
images: {$push: '$images'}
}}
], function (err, result) {
if (err) {
console.log(err);
return;
}
console.log(result);
});
当数组不为空但仅包含 deleted 为 null 的对象时,现在会出现问题。我 $unwind
图像,但 $match
没有找到任何匹配项,因此无法执行最后的 $group
。
我正在考虑在管道的开头推入虚拟对象,然后在结束时对图像进行计数。如果虚拟对象是唯一的,它将保留下来,但如果有其他图像对象已经通过管道,则需要将其删除。
如果这是一条明智的路线,我会很高兴得到一些指示。如果我的想法偏离了方向,我们将不胜感激地收到任何引导我朝着正确方向前进的提示。
谢谢。
现代 MongoDB 版本当然只是应用 $filter
and $addFields
将过滤后的可能为空的数组结果写入文档:
Collection.aggregate([
{ "$addFields": {
"images": {
"$filter": {
"input": "$filter",
"as": "i",
"cond": { "$eq": [ "$$i.deleted", null ] }
}
}
}}
])
你之前得到的答案不是一个非常现代或有效的答案,因为有其他更好的方法来处理过滤数组中的内容,而不是 $unwind
、$match
和$group
.
与 MongoDB 2.6 一样,您可以使用唯一的数组标识符来做到这一点:
Collection.aggregate([
{ "$project": {
"name": 1,
"images": { "$cond": [
{ "$eq": [{ "$size": { "$ifNull": [ "$images",[]] }}, 0] },
{ "$ifNull": [ "$images", [] ] },
{ "$setDifference": [
{ "$map": {
"input": "$images",
"as": "i",
"in": { "$cond": [
{ "$eq": [ "$$i.deleted", null ] },
"$$i",
false
]}
}},
[false]
]}
]}
}}
],
$map
operator transforms arrays in place in the document by returning each inspected element evaluated by the given expression. Here you can use $cond
为了测试字段值并决定是否 return 字段原样或其他 return false
.
$setDifference
操作 "compares" 生成的变换数组与另一个奇异元素数组 [false]
。这具有从数组中删除所有不匹配项的效果,并只留下那些项,甚至留下一个没有匹配项的空数组。
只要您的文档不在文档的多个级别包含相同的引用 属性,以下带有 $redact
的内容就是安全的。看起来有些滑稽的情况是因为 "deleted": null
属性 实际上被投影(用于评估目的)在它不存在的级别。这是必需的,因为 $redact
以 "recursive" 的方式使用,下降文档树以决定它摆脱什么,或者 "redacts":
Collection.aggregate([
{ "$redact": {
"$cond": [
{ "$eq": [ { "$ifNull": [ "$deleted", null ] }, null ] },
"$$DESCEND",
"$$PRUNE"
]
}}
]
这确实是为您的特定目的而实施的最简单的逻辑。请记住,如果您在文档中添加另一个 "deleted" 字段以某种方式表示其他含义,您以后可能无法使用它。
如果你真的被 2.6 之前的 MongoDB 版本所困,并且无法访问这些操作,那么你当然需要执行 $unwind
、$match
和 $group
处理。因此,在开始时需要注意空数组或缺失数组,以及匹配没有匹配项的数组时。
一种方法:
Collection.aggregate([
// Cater for missing or empty arrays
{ "$project": {
"name": 1,
"images": { "$cond": [
{ "$eq": [{ "$ifNull": [ "$images", [] ] }, [] ] },
{ "$const": [{ "deleted": false }] },
"$images"
]}
}},
// Safe to unwind
{ "$unwind": "$images" },
// Just count the matched array entries first
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"images": { "$push": "$images" },
"count": { "$sum": { "$cond": [
{ "$eq": [ "$images.deleted", null ] },
1,
0
]}}
}},
// Unwind again
{ "$unwind": "$images" },
// Match either non deleted or unmatched array
{ "$match": {
"$or": [
{ "images.deleted": null},
{ "count": 0 }
]
}},
// Group back with the things that were matched
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"images": { "$push": "$images" },
"count": { "$first": "$count" }
}},
// Replace the un-matched arrays with empty ones
{ "$project": {
"name": 1,
"images": { "$cond": [
{ "$eq": [ "$count", 0 ] },
[],
"$images"
]}
}}
],
所以那里有更多的提升,但一般原则是只获得 "count" 匹配元素,当你过滤时,你还保留数组项 0
"count" 但稍后只需替换整个数组。
您还可以在这里考虑,如果您首先在文档中维护 "activeCount" 字段,那么您将不需要计算它并删除几个阶段。
当然,这里的另一个论点是,您可以通过在单独的数组中实际维护 "active" 和 "deleted" 项来省去这方面的麻烦。每次更新都这样做可以消除通过聚合进行过滤的任何需要。我想这完全取决于您的真实目的。但深思。
当然这都是根据您的原始数据进行的测试,并进行了一些修改以适应测试用例:
{
"_id" : ObjectId("54ec9cac83a214491d2110f4"),
"name" : "my_images",
"images" : [
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2311026b0cb289ed04188"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:20:16.961Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2314a26b0cb289ed04189"),
"deleted" : ISODate("2015-02-24T15:38:14.826Z"),
"date_added" : ISODate("2015-02-28T21:21:14.910Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315526b0cb289ed0418a"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:25.042Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315d26b0cb289ed0418b"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:33.081Z")
}
]
},
{
"_id" : ObjectId("54fa6ca87c105bc872cc1886"),
"name" : "another",
"images" : [ ]
},
{
"_id" : ObjectId("54fa6cef7c105bc872cc1887"),
"name" : "final",
"images" : [
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2314a26b0cb289ed04189"),
"deleted" : ISODate("2015-02-24T15:38:14.826Z"),
"date_added" : ISODate("2015-02-28T21:21:14.910Z")
}
]
}
所有版本都产生安全结果:
{
"_id" : ObjectId("54ec9cac83a214491d2110f4"),
"name" : "my_images",
"images" : [
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2311026b0cb289ed04188"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:20:16.961Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315526b0cb289ed0418a"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:25.042Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315d26b0cb289ed0418b"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:33.081Z")
}
]
},
{
"_id" : ObjectId("54fa6ca87c105bc872cc1886"),
"name" : "another",
"images" : [ ]
},
{
"_id" : ObjectId("54fa6cef7c105bc872cc1887"),
"name" : "final",
"images" : [ ]
}
这真是一个deleted != null
。我发现当数组中没有项目时我的 $unwind
管道坏了,所以我还实现了一个 $cond
来添加一个数组为空的虚拟对象。这是到目前为止的代码:
Collection.aggregate([
{ $match:
{ _id: ObjectID(collection_id) }
},
{
$project:
{
_id: 1,
name: 1,
images: {
$cond:
[
{
$eq:
[
"$images",
[]
]
},
[
dummyImg // Variable containing dummy object
],
'$images'
]
},
}
},
{ $unwind: "$images" },
{ $match:
{ "images.deleted": null }
},
// Regroup the docs by _id to reassemble the images array
{$group: {
_id: '$_id',
name: {$first: '$name'},
images: {$push: '$images'}
}}
], function (err, result) {
if (err) {
console.log(err);
return;
}
console.log(result);
});
当数组不为空但仅包含 deleted 为 null 的对象时,现在会出现问题。我 $unwind
图像,但 $match
没有找到任何匹配项,因此无法执行最后的 $group
。
我正在考虑在管道的开头推入虚拟对象,然后在结束时对图像进行计数。如果虚拟对象是唯一的,它将保留下来,但如果有其他图像对象已经通过管道,则需要将其删除。
如果这是一条明智的路线,我会很高兴得到一些指示。如果我的想法偏离了方向,我们将不胜感激地收到任何引导我朝着正确方向前进的提示。
谢谢。
现代 MongoDB 版本当然只是应用 $filter
and $addFields
将过滤后的可能为空的数组结果写入文档:
Collection.aggregate([
{ "$addFields": {
"images": {
"$filter": {
"input": "$filter",
"as": "i",
"cond": { "$eq": [ "$$i.deleted", null ] }
}
}
}}
])
你之前得到的答案不是一个非常现代或有效的答案,因为有其他更好的方法来处理过滤数组中的内容,而不是 $unwind
、$match
和$group
.
与 MongoDB 2.6 一样,您可以使用唯一的数组标识符来做到这一点:
Collection.aggregate([
{ "$project": {
"name": 1,
"images": { "$cond": [
{ "$eq": [{ "$size": { "$ifNull": [ "$images",[]] }}, 0] },
{ "$ifNull": [ "$images", [] ] },
{ "$setDifference": [
{ "$map": {
"input": "$images",
"as": "i",
"in": { "$cond": [
{ "$eq": [ "$$i.deleted", null ] },
"$$i",
false
]}
}},
[false]
]}
]}
}}
],
$map
operator transforms arrays in place in the document by returning each inspected element evaluated by the given expression. Here you can use $cond
为了测试字段值并决定是否 return 字段原样或其他 return false
.
$setDifference
操作 "compares" 生成的变换数组与另一个奇异元素数组 [false]
。这具有从数组中删除所有不匹配项的效果,并只留下那些项,甚至留下一个没有匹配项的空数组。
只要您的文档不在文档的多个级别包含相同的引用 属性,以下带有 $redact
的内容就是安全的。看起来有些滑稽的情况是因为 "deleted": null
属性 实际上被投影(用于评估目的)在它不存在的级别。这是必需的,因为 $redact
以 "recursive" 的方式使用,下降文档树以决定它摆脱什么,或者 "redacts":
Collection.aggregate([
{ "$redact": {
"$cond": [
{ "$eq": [ { "$ifNull": [ "$deleted", null ] }, null ] },
"$$DESCEND",
"$$PRUNE"
]
}}
]
这确实是为您的特定目的而实施的最简单的逻辑。请记住,如果您在文档中添加另一个 "deleted" 字段以某种方式表示其他含义,您以后可能无法使用它。
如果你真的被 2.6 之前的 MongoDB 版本所困,并且无法访问这些操作,那么你当然需要执行 $unwind
、$match
和 $group
处理。因此,在开始时需要注意空数组或缺失数组,以及匹配没有匹配项的数组时。
一种方法:
Collection.aggregate([
// Cater for missing or empty arrays
{ "$project": {
"name": 1,
"images": { "$cond": [
{ "$eq": [{ "$ifNull": [ "$images", [] ] }, [] ] },
{ "$const": [{ "deleted": false }] },
"$images"
]}
}},
// Safe to unwind
{ "$unwind": "$images" },
// Just count the matched array entries first
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"images": { "$push": "$images" },
"count": { "$sum": { "$cond": [
{ "$eq": [ "$images.deleted", null ] },
1,
0
]}}
}},
// Unwind again
{ "$unwind": "$images" },
// Match either non deleted or unmatched array
{ "$match": {
"$or": [
{ "images.deleted": null},
{ "count": 0 }
]
}},
// Group back with the things that were matched
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"images": { "$push": "$images" },
"count": { "$first": "$count" }
}},
// Replace the un-matched arrays with empty ones
{ "$project": {
"name": 1,
"images": { "$cond": [
{ "$eq": [ "$count", 0 ] },
[],
"$images"
]}
}}
],
所以那里有更多的提升,但一般原则是只获得 "count" 匹配元素,当你过滤时,你还保留数组项 0
"count" 但稍后只需替换整个数组。
您还可以在这里考虑,如果您首先在文档中维护 "activeCount" 字段,那么您将不需要计算它并删除几个阶段。
当然,这里的另一个论点是,您可以通过在单独的数组中实际维护 "active" 和 "deleted" 项来省去这方面的麻烦。每次更新都这样做可以消除通过聚合进行过滤的任何需要。我想这完全取决于您的真实目的。但深思。
当然这都是根据您的原始数据进行的测试,并进行了一些修改以适应测试用例:
{
"_id" : ObjectId("54ec9cac83a214491d2110f4"),
"name" : "my_images",
"images" : [
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2311026b0cb289ed04188"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:20:16.961Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2314a26b0cb289ed04189"),
"deleted" : ISODate("2015-02-24T15:38:14.826Z"),
"date_added" : ISODate("2015-02-28T21:21:14.910Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315526b0cb289ed0418a"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:25.042Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315d26b0cb289ed0418b"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:33.081Z")
}
]
},
{
"_id" : ObjectId("54fa6ca87c105bc872cc1886"),
"name" : "another",
"images" : [ ]
},
{
"_id" : ObjectId("54fa6cef7c105bc872cc1887"),
"name" : "final",
"images" : [
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2314a26b0cb289ed04189"),
"deleted" : ISODate("2015-02-24T15:38:14.826Z"),
"date_added" : ISODate("2015-02-28T21:21:14.910Z")
}
]
}
所有版本都产生安全结果:
{
"_id" : ObjectId("54ec9cac83a214491d2110f4"),
"name" : "my_images",
"images" : [
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2311026b0cb289ed04188"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:20:16.961Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315526b0cb289ed0418a"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:25.042Z")
},
{
"ext" : "jpeg",
"type" : "image/jpeg",
"_id" : ObjectId("54f2315d26b0cb289ed0418b"),
"deleted" : null,
"date_added" : ISODate("2015-02-28T21:21:33.081Z")
}
]
},
{
"_id" : ObjectId("54fa6ca87c105bc872cc1886"),
"name" : "another",
"images" : [ ]
},
{
"_id" : ObjectId("54fa6cef7c105bc872cc1887"),
"name" : "final",
"images" : [ ]
}