在MongoDB聚合框架中,我希望在对象(即JSON集合)上使用$ unwind运算符。看起来不是possible,是否有解决方法?有计划实施吗?
例如,从聚合documentation中获取文章集合。假设有一个额外的字段“评级”是来自用户的地图 - >评分。你能算出每个用户的平均评分吗?
除此之外,我对聚合框架非常满意。
更新:这是每个请求的JSON集合的简化版本。我正在存储基因组数据。我不能真正使基因型成为一个数组,因为最常见的查找是获取随机人的基因型。
variants: [
{
name: 'variant1',
genotypes: {
person1: 2,
person2: 5,
person3: 7,
}
},
{
name: 'variant2',
genotypes: {
person1: 3,
person2: 3,
person3: 2,
}
}
]
答案 0 :(得分:29)
您无法使用聚合框架进行描述的计算类型 - 并且不,因为非数组没有$unwind
方法。即使person:value对象是数组中的文档,$unwind
也无济于事。
“分组依据”功能(无论是在MongoDB中还是在任何关系数据库中)都是对字段或列的值完成的。我们根据另一个字段的值按字段值和sum / average / etc进行分组。
简单的示例是您建议的变体,评级字段添加到示例文章集合中,但不是作为从用户到评级的映射,而是作为这样的数组:
{ title : title of article", ...
ratings: [
{ voter: "user1", score: 5 },
{ voter: "user2", score: 8 },
{ voter: "user3", score: 7 }
]
}
现在您可以将其聚合为:
[ {$unwind: "$ratings"},
{$group : {_id : "$ratings.voter", averageScore: {$avg:"$ratings.score"} } }
]
但是这个结构如你所描述的那样看起来像这样:
{ title : title of article", ...
ratings: {
user1: 5,
user2: 8,
user3: 7
}
}
甚至是这样:
{ title : title of article", ...
ratings: [
{ user1: 5 },
{ user2: 8 },
{ user3: 7 }
]
}
即使您可以$unwind
这个,也没有什么可以聚合在这里。除非您知道所有可能的密钥(用户)的完整列表,否则您无法做到这一点。 [*]
与您所拥有的类似的关系数据库架构:
CREATE TABLE T (
user1: integer,
user2: integer,
user3: integer
...
);
这不是将要做的,而是我们会这样做:
CREATE TABLE T (
username: varchar(32),
score: integer
);
现在我们使用SQL聚合:
select username, avg(score) from T group by username;
MongoDB有一个增强请求,可能允许您将来在聚合框架中执行此操作 - 将值投影到键的能力,反之亦然。同时,总有map / reduce。
[*]如果您知道所有唯一键(您可以使用类似于this的方法找到所有唯一键),有一种复杂的方法可以执行此操作但如果您知道所有键,那么您也可以为每个userX运行一系列db.articles.find({"ratings.user1":{$exists:true}},{_id:0,"ratings.user1":1})
形式的查询,这些查询将返回所有评级,您可以简单地对它们进行求和和平均,而不是进行聚合框架所需的非常复杂的投影。
答案 1 :(得分:7)
从3.4.4开始,您可以使用$ objectToArray
将对象转换为数组请参阅: https://docs.mongodb.com/manual/reference/operator/aggregation/objectToArray/
答案 2 :(得分:0)
这是一个老问题,但我通过试验和错误遇到了一些信息,人们可能会觉得这些信息很有用。
实际上可以通过这种方式欺骗解析器来解除虚拟值:
db.Opportunity.aggregate(
{ $project: {
Field1: 1, Field2: 1, Field3: 1,
DummyUnwindField: { $ifNull: [null, [1.0]] }
}
},
{ $unwind: "$DummyUnwindField" }
);
无论值是否存在,这将为每个文档生成1行。您可以修改它以生成所需的结果。我原本希望将它与多个$ unwinds结合起来(有点像map / reduce中的emit()),但是唉,最后一个$ unwind赢了,或者它们组合成一个交集而不是联合,这使得它无法实现结果我正在寻找。令我遗憾的是对聚合框架功能感到失望,因为它不适合我希望使用它的一个用例(并且看起来奇怪地就像这个领域的StackOverflow上的很多问题一样) - 根据匹配排序结果率。改善糟糕的地图会降低性能会使整个功能变得不必要。
答案 3 :(得分:0)
This is what I found & extended.
让我们在mongo中创建实验数据库
db.copyDatabase('livedb' , 'experimentdb')
现在使用experimentdb&在您的experimentcollection中将Array转换为object
db.getCollection('experimentcollection').find({}).forEach(function(e){
if(e.store){
e.ratings = [e.ratings]; //Objects name to be converted to array eg:ratings
db.experimentcollection.save(e);
}
})
将json转换为平面对象的一些书呆子js代码
var flatArray = [];
var data = db.experimentcollection.find().toArray();
for (var index = 0; index < data.length; index++) {
var flatObject = {};
for (var prop in data[index]) {
var value = data[index][prop];
if (Array.isArray(value) && prop === 'ratings') {
for (var i = 0; i < value.length; i++) {
for (var inProp in value[i]) {
flatObject[inProp] = value[i][inProp];
}
}
}else{
flatObject[prop] = value;
}
}
flatArray.push(flatObject);
}
printjson(flatArray);