我有一个包含以下文件的集合:
$owner_questions
现在我想要的是根据以下数组项计算文档总数:
{
_id: ObjectId("000000000000000000059734"),
locations: ["A", "B", "C"]
},
{
_id: ObjectId("000000000000000000059735"),
locations: ["A", "D", "K"]
},
{
_id: ObjectId("000000000000000000059736"),
locations: ["1", "3", "C"]
}
我想要的结果是:
let array = ['A', 'B', '1'];
我尝试过:
{
'A': 2,
'B': 1,
'1': 1
}
但我的查询结果格式与我想要的不一样。
感谢任何帮助和指导。
答案 0 :(得分:1)
如果你至少有MongoDB 3.4.4,那么你可以这样做:
var array = ['A', 'B', '1'];
db.getCollection('mycollection').aggregate([
{ "$project": {
"locations": {
"$map": {
"input": {
"$filter": {
"input": "$locations",
"cond": { "$in": [ "$$this", array ] }
}
},
"in": { "k": "$$this", "v": 1 }
}
}
}},
{ "$unwind": "$locations" },
{ "$group": {
"_id": "$locations.k",
"v": { "$sum": "$locations.v" }
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": null,
"obj": { "$push": { "k": "$_id", "v": "$v" } }
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$obj" }
}}
])
对于没有$arrayToObject
之类的旧版本,您可以在从服务器返回结果后“转换”结果,如下所示:
var array = ['A', 'B', '1'];
db.getCollection('mycollection').aggregate([
{ "$project": {
"locations": {
"$map": {
"input": {
"$filter": {
"input": "$locations",
"cond": {
// "$in": [ "$$this", array ]
"$or": array.map(a => ({ "$eq": [ "$$this", a ] }) )
}
}
},
"in": { "k": "$$this", "v": 1 }
}
}
}},
{ "$unwind": "$locations" },
{ "$group": {
"_id": "$locations.k",
"v": { "$sum": "$locations.v" }
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": null,
"obj": { "$push": { "k": "$_id", "v": "$v" } }
}},
/*
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$obj" }
}}
*/
]).map(d =>
d.obj.reduce((acc,curr) => Object.assign(acc,{ [curr.k]: curr.v }),{})
)
在任何一种情况下,第一阶段都是$project
$map
,以便查看文档数组中的每个值并将其与比较数组进行比较。实际上,我们使用$filter
只返回“匹配”,$map
返回值1
来计算每次匹配。
对于支持运算符的版本,使用$in
进行“过滤”有两种基本方法,或者在引入之前使用旧版本中的$or
。
坦率地说,只要您的文档数据是“唯一的”,就可以简单地使用$setIntersection
来获取匹配项,因为没有文档数组包含多个值的出现。所以我在这里玩$filter
安全,因为我不知道你的数据。选择适合的任何一种。
// If the "locations" content is meant to be "unique"
{ "$project": {
"locations": {
"$map": {
"input": {
"$setIntersection": [ "$locations", array ]
},
"in": { "k": "$$this", "v": 1 }
}
}
}},
请注意k
和v
属性表单中的$map
输出。这将继续作为管道其余部分的模式。
由于您希望对数组项的k
值进行“聚合”,因此我们使用$unwind
,因此我们可以跨文档将这些值一起添加。然后通过$group
将k
的值与每个v
上的$sum
相关联,以有效地“计算”这些事件。
$sort
完全是可选的,实际上你不应该关心单个输出文档中键的顺序。请注意与“欲望”的区别,但这只是一个明显的事实,即"1"
在词汇上“少于”"A"
。所以你无法与之抗争,而这正是世界的运作方式。
下一阶段只需$group
到单个文档即可。在这里,我们继续重建为包含k
和v
的对象的“数组”。
原因在于最终处理。您拥有支持$arrayToObject
的MongoDB的地方(自3.4.4以来实际包含,但文档声明3.6)。在你做的地方,我们只是在$replaceRoot
阶段内提供生成的“数组”作为输入,以便返回最终输出。
如果您没有该功能,则可以处理光标结果(此处使用shell Cursor.map()
显示)并在进一步处理之前转换文档。任何迭代器方法都可以,大多数驱动程序都有Cursor.map()
。这并不重要,因为在这种情况下聚合管道会产生一个文档。
在现代shell版本中使用的JavaScript方法是简单地在数组上应用.reduce()
并将输出对象转换为所需的对象输出。它基本上与服务器完全相同,但只是在客户端代码中。
任何一种形式都会返回所需的结果:
{
"1" : 1.0,
"A" : 2.0,
"B" : 1.0
}
答案 1 :(得分:0)
db.mycollection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: '$locations'
}
},
// Stage 2
{
$match: {
locations: {
$in: ['A', 'B', '1']
}
}
},
// Stage 3
{
$group: {
_id: '$locations',
total: {
$sum: 1
}
}
}
]
);