MongoDB 聚合:计算对象数组中的出现次数并转换为对象
MongoDB Aggregate: count occurrences in array of object and transform to object
我有一个名为 notifications
的集合,它有 2 个字段(为简单起见):
targets
是一个字符串数组
userReads
是对象数组 {userId, readAt}
示例:
{
targets: ['group1', 'group2'],
userReads: [
{userId: 1, readAt: 'date1'},
{userId: 2, readAt: 'date2'},
{userId: 3, readAt: 'date3'},
]
}
{
targets: ['group1'],
userReads: [
{userId: 1, readAt: 'date4'}
]
}
期望的输出:
{
groupsNotificationsCount: {
group1: 2,
group2: 1
},
usersNotificationsCount: {
1: 2,
2: 1,
3: 1
}
}
起初我尝试了这个聚合:
[{
$match: {
targets: {
$in: ['group/1','group/2']
},
}
}, {
$project: {
targets: 1,
userReads: 1
}
}, {
$unwind: {
path: '$targets'
}
}, {
$match: {
targets: {
$in: ['group/1','group/2']
}
}
}, {
$group: {
_id: '$targets',
countForGroup: {
$count: {}
},
userReads: {
$push: '$userReads'
}
}
}, {
$addFields: {
userReads: {
$reduce: {
input: '$userReads',
initialValue: [],
'in': {
$concatArrays: [
'$$value',
'$$this'
]
}
}
}
}
}]
我的文档中每个组的计数都是正确的,如下所示:
{
_id: 'group1',
countForGroup: 2,
userReads: [
{userId: 1, readAt: 'date1'},
{userId: 2, readAt: 'date2'},
{userId: 3, readAt: 'date3'},
{userId: 1, readAt: 'date4'},
]
}
但现在我不知道如何从那里继续获得我想要的输出
这是使用 $facet
的完美用例。您可以通过 $unwind
和 $group
计数分别处理 groupsNotificationsCount
和 usersNotificationsCount
。之后通过争论成 k-v 元组数组,您可以使用 $arrayToObject
构造您期望的形式
db.collection.aggregate([
{
"$facet": {
"groupsNotificationsCount": [
{
"$unwind": "$targets"
},
{
$group: {
_id: "$targets",
count: {
$sum: 1
}
}
}
],
"usersNotificationsCount": [
{
"$unwind": "$userReads"
},
{
$group: {
_id: "$userReads.userId",
count: {
$sum: 1
}
}
}
]
}
},
{
"$project": {
groupsNotificationsCount: {
"$arrayToObject": {
"$map": {
"input": "$groupsNotificationsCount",
"as": "g",
"in": {
k: "$$g._id",
v: "$$g.count"
}
}
}
},
usersNotificationsCount: {
"$arrayToObject": {
"$map": {
"input": "$usersNotificationsCount",
"as": "u",
"in": {
k: {
$toString: "$$u._id"
},
v: "$$u.count"
}
}
}
}
}
}
])
这里是Mongo playground供大家参考。
我有一个名为 notifications
的集合,它有 2 个字段(为简单起见):
targets
是一个字符串数组userReads
是对象数组{userId, readAt}
示例:
{
targets: ['group1', 'group2'],
userReads: [
{userId: 1, readAt: 'date1'},
{userId: 2, readAt: 'date2'},
{userId: 3, readAt: 'date3'},
]
}
{
targets: ['group1'],
userReads: [
{userId: 1, readAt: 'date4'}
]
}
期望的输出:
{
groupsNotificationsCount: {
group1: 2,
group2: 1
},
usersNotificationsCount: {
1: 2,
2: 1,
3: 1
}
}
起初我尝试了这个聚合:
[{
$match: {
targets: {
$in: ['group/1','group/2']
},
}
}, {
$project: {
targets: 1,
userReads: 1
}
}, {
$unwind: {
path: '$targets'
}
}, {
$match: {
targets: {
$in: ['group/1','group/2']
}
}
}, {
$group: {
_id: '$targets',
countForGroup: {
$count: {}
},
userReads: {
$push: '$userReads'
}
}
}, {
$addFields: {
userReads: {
$reduce: {
input: '$userReads',
initialValue: [],
'in': {
$concatArrays: [
'$$value',
'$$this'
]
}
}
}
}
}]
我的文档中每个组的计数都是正确的,如下所示:
{
_id: 'group1',
countForGroup: 2,
userReads: [
{userId: 1, readAt: 'date1'},
{userId: 2, readAt: 'date2'},
{userId: 3, readAt: 'date3'},
{userId: 1, readAt: 'date4'},
]
}
但现在我不知道如何从那里继续获得我想要的输出
这是使用 $facet
的完美用例。您可以通过 $unwind
和 $group
计数分别处理 groupsNotificationsCount
和 usersNotificationsCount
。之后通过争论成 k-v 元组数组,您可以使用 $arrayToObject
db.collection.aggregate([
{
"$facet": {
"groupsNotificationsCount": [
{
"$unwind": "$targets"
},
{
$group: {
_id: "$targets",
count: {
$sum: 1
}
}
}
],
"usersNotificationsCount": [
{
"$unwind": "$userReads"
},
{
$group: {
_id: "$userReads.userId",
count: {
$sum: 1
}
}
}
]
}
},
{
"$project": {
groupsNotificationsCount: {
"$arrayToObject": {
"$map": {
"input": "$groupsNotificationsCount",
"as": "g",
"in": {
k: "$$g._id",
v: "$$g.count"
}
}
}
},
usersNotificationsCount: {
"$arrayToObject": {
"$map": {
"input": "$usersNotificationsCount",
"as": "u",
"in": {
k: {
$toString: "$$u._id"
},
v: "$$u.count"
}
}
}
}
}
}
])
这里是Mongo playground供大家参考。