计算MongoDB聚合框架中的中值

计算MongoDB聚合框架中的中值,mongodb,aggregation-framework,median,Mongodb,Aggregation Framework,Median,有没有办法使用MongoDB聚合框架计算中值?聚合框架不支持开箱即用的中值。所以你必须自己写点东西 我建议您在应用程序级别执行此操作。使用普通find()检索所有文档,对结果集进行排序(在datbase上使用光标的.sort()功能或在应用程序中对它们进行排序-您的决定),然后获取元素大小/2 如果您真的想在数据库级别执行此操作,可以使用map reduce执行。map函数将发出一个键和一个带有单个值的数组,该值是您想要得到的中值。reduce函数只是将它接收到的结果的数组连接起来,因此每个键最

有没有办法使用MongoDB聚合框架计算中值?

聚合框架不支持开箱即用的中值。所以你必须自己写点东西

我建议您在应用程序级别执行此操作。使用普通find()检索所有文档,对结果集进行排序(在datbase上使用光标的
.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

AFAIK没有
$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 }