计算MongoDB聚合框架中的中值
有没有办法使用MongoDB聚合框架计算中值?聚合框架不支持开箱即用的中值。所以你必须自己写点东西 我建议您在应用程序级别执行此操作。使用普通find()检索所有文档,对结果集进行排序(在datbase上使用光标的计算MongoDB聚合框架中的中值,mongodb,aggregation-framework,median,Mongodb,Aggregation Framework,Median,有没有办法使用MongoDB聚合框架计算中值?聚合框架不支持开箱即用的中值。所以你必须自己写点东西 我建议您在应用程序级别执行此操作。使用普通find()检索所有文档,对结果集进行排序(在datbase上使用光标的.sort()功能或在应用程序中对它们进行排序-您的决定),然后获取元素大小/2 如果您真的想在数据库级别执行此操作,可以使用map reduce执行。map函数将发出一个键和一个带有单个值的数组,该值是您想要得到的中值。reduce函数只是将它接收到的结果的数组连接起来,因此每个键最
.sort()
功能或在应用程序中对它们进行排序-您的决定),然后获取元素大小/2
如果您真的想在数据库级别执行此操作,可以使用map reduce执行。map函数将发出一个键和一个带有单个值的数组,该值是您想要得到的中值。reduce函数只是将它接收到的结果的数组连接起来,因此每个键最终都会得到一个包含所有值的数组。finalize函数将计算该数组的中值,再次通过对数组进行排序,然后获得元素编号
size/2
在一般情况下,中值的计算有些棘手,因为它涉及对整个数据集进行排序,或者使用深度与数据集大小成比例的递归。这可能就是为什么许多数据库没有现成的中间运算符(MySQL也没有)
计算中位数的最简单方法是使用这两条语句(假设我们要计算中位数的属性名为a
,我们希望它覆盖集合中的所有文档,coll
):
这相当于人们所做的。使用聚合框架可以一次性完成 排序=>放入数组排序值=>获取数组大小=>将大小除以2=>获取除法的Int值(中间值的左侧)=>将1添加到左侧(右侧)=>获取左侧和右侧的数组元素=>两个元素的平均值 这是Spring java mongoTemplate的一个示例: 该模型是一个由作者(“所有者”)登录的图书列表,目标是获得用户图书的中位数:
GroupOperation countByBookOwner = group("owner").count().as("nbBooks");
SortOperation sortByCount = sort(Direction.ASC, "nbBooks");
GroupOperation putInArray = group().push("nbBooks").as("nbBooksArray");
ProjectionOperation getSizeOfArray = project("nbBooksArray").and("nbBooksArray").size().as("size");
ProjectionOperation divideSizeByTwo = project("nbBooksArray").and("size").divide(2).as("middleFloat");
ProjectionOperation getIntValueOfDivisionForBornLeft = project("middleFloat", "nbBooksArray").and("middleFloat")
.project("trunc").as("beginMiddle");
ProjectionOperation add1ToBornLeftToGetBornRight = project("beginMiddle", "middleFloat", "nbBooksArray")
.and("beginMiddle").project("add", 1).as("endMiddle");
ProjectionOperation arrayElementAt = project("beginMiddle", "endMiddle", "middleFloat", "nbBooksArray")
.and("nbBooksArray").project("arrayElemAt", "$beginMiddle").as("beginValue").and("nbBooksArray")
.project("arrayElemAt", "$endMiddle").as("endValue");
ProjectionOperation averageForMedian = project("beginMiddle", "endMiddle", "middleFloat", "nbBooksArray",
"beginValue", "endValue").and("beginValue").project("avg", "$endValue").as("median");
Aggregation aggregation = newAggregation(countByBookOwner, sortByCount, putInArray, getSizeOfArray,
divideSizeByTwo, getIntValueOfDivisionForBornLeft, add1ToBornLeftToGetBornRight, arrayElementAt,
averageForMedian);
long time = System.currentTimeMillis();
AggregationResults<MedianContainer> groupResults = mongoTemplate.aggregate(aggregation, "book",
MedianContainer.class);
}虽然不准确,但它确实指引了我正确的方向。给定解决方案的问题在于,它只在记录数为偶数时才起作用。因为对于奇数记录,只需取中点的值,而无需计算平均值
我就是这样让它工作的
db.collection.aggregate([
{ "$match": { "processingStatus": "Completed" } },
{
"$group": {
"_id": "$userId",
"valueArray": {
"$push": "$value"
}
}
},
{ "$sort": { "value": 1 } },
{
"$project": {
"_id": 0,
"userId": "$_id",
"valueArray": 1,
"size": { "$size": ["$valueArray"] }
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"isEvenLength": { "$eq": [{ "$mod": ["$size", 2] }, 0 ] },
"middlePoint": { "$trunc": { "$divide": ["$size", 2] } }
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"isEvenLength": 1,
"middlePoint": 1,
"beginMiddle": { "$subtract": [ "$middlePoint", 1] },
"endMiddle": "$middlePoint"
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"middlePoint": 1,
"beginMiddle": 1,
"beginValue": { "$arrayElemAt": ["$stepsArray", "$beginMiddle"] },
"endValue": { "$arrayElemAt": ["$stepsArray", "$endMiddle"] },
"isEvenLength": 1
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"middlePoint": 1,
"beginMiddle": 1,
"beginValue": 1,
"endValue": 1,
"middleSum": { "$add": ["$beginValue", "$endValue"] },
"isEvenLength": 1
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"median": {
"$cond": {
if: "$isEvenLength",
then: { "$divide": ["$middleSum", 2] },
else: { "$arrayElemAt": ["$stepsArray", "$middlePoint"] }
}
}
}
}
])
从
mongo4.4
开始,$group
阶段有一个新的聚合操作符,允许通过javascript用户定义函数在文档分组时自定义累积文档
因此,为了找到中间值:
// { "a" : 25, "b" : 12 }
// { "a" : 89, "b" : 7 }
// { "a" : 25, "b" : 17 }
// { "a" : 25, "b" : 24 }
// { "a" : 89, "b" : 15 }
db.collection.aggregate([
{ $group: {
_id: "$a",
median: {
$accumulator: {
accumulateArgs: ["$b"],
init: function() { return []; },
accumulate: function(bs, b) { return bs.concat(b); },
merge: function(bs1, bs2) { return bs1.concat(bs2); },
finalize: function(bs) {
bs.sort(function(a, b) { return a - b });
var mid = bs.length / 2;
return mid % 1 ? bs[mid - 0.5] : (bs[mid - 1] + bs[mid]) / 2;
},
lang: "js"
}
}
}}
])
// { "_id" : 25, "median" : 17 }
// { "_id" : 89, "median" : 11 }
蓄能器:
- 在字段
上累加(b
)acculateargs
- 初始化为空数组(
)init
- 在数组中累积
项(b
和acculate
)merge
- 最后对
项执行中值计算(b
)finalize
$median
这样的东西,因此您可能必须使用map reduce进行此操作。有一个开放的功能请求,以添加对$median
累加器的支持。请在MongoDB问题跟踪器中投票/观看。我知道不允许仅仅为了表示感谢而发表评论。。但是这很漂亮:)警告:这代码很轻,但是服务器很重,我不知道这是否有效,只是为了奉献投票。嗨,这是解决问题的一个很好的方法。但我正在做一个Java项目,需要同样的东西。你知道我在哪里可以找到关于这个的相关文档吗。非常感谢。
db.collection.aggregate([
{ "$match": { "processingStatus": "Completed" } },
{
"$group": {
"_id": "$userId",
"valueArray": {
"$push": "$value"
}
}
},
{ "$sort": { "value": 1 } },
{
"$project": {
"_id": 0,
"userId": "$_id",
"valueArray": 1,
"size": { "$size": ["$valueArray"] }
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"isEvenLength": { "$eq": [{ "$mod": ["$size", 2] }, 0 ] },
"middlePoint": { "$trunc": { "$divide": ["$size", 2] } }
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"isEvenLength": 1,
"middlePoint": 1,
"beginMiddle": { "$subtract": [ "$middlePoint", 1] },
"endMiddle": "$middlePoint"
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"middlePoint": 1,
"beginMiddle": 1,
"beginValue": { "$arrayElemAt": ["$stepsArray", "$beginMiddle"] },
"endValue": { "$arrayElemAt": ["$stepsArray", "$endMiddle"] },
"isEvenLength": 1
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"middlePoint": 1,
"beginMiddle": 1,
"beginValue": 1,
"endValue": 1,
"middleSum": { "$add": ["$beginValue", "$endValue"] },
"isEvenLength": 1
}
},
{
"$project": {
"userId": 1,
"valueArray": 1,
"median": {
"$cond": {
if: "$isEvenLength",
then: { "$divide": ["$middleSum", 2] },
else: { "$arrayElemAt": ["$stepsArray", "$middlePoint"] }
}
}
}
}
])
// { "a" : 25, "b" : 12 }
// { "a" : 89, "b" : 7 }
// { "a" : 25, "b" : 17 }
// { "a" : 25, "b" : 24 }
// { "a" : 89, "b" : 15 }
db.collection.aggregate([
{ $group: {
_id: "$a",
median: {
$accumulator: {
accumulateArgs: ["$b"],
init: function() { return []; },
accumulate: function(bs, b) { return bs.concat(b); },
merge: function(bs1, bs2) { return bs1.concat(bs2); },
finalize: function(bs) {
bs.sort(function(a, b) { return a - b });
var mid = bs.length / 2;
return mid % 1 ? bs[mid - 0.5] : (bs[mid - 1] + bs[mid]) / 2;
},
lang: "js"
}
}
}}
])
// { "_id" : 25, "median" : 17 }
// { "_id" : 89, "median" : 11 }