MongoDB集約フレームワークを使用して中央値を計算する方法はありますか?
中央値は、データセット全体を並べ替えたり、データセットのサイズにも比例する深さの再帰を使用したりするため、一般的なケースでは計算がやや難しいです。これが、多くのデータベースにすぐに使用できる中央値演算子がない理由かもしれません(MySQLにも中央値演算子がありません)。
中央値を計算する最も簡単な方法は、これら2つのステートメントを使用することです(中央値を計算する属性がa
と呼ばれ、コレクション内のすべてのドキュメントにcoll
が必要であると仮定します。 )::
count = db.coll.count();
db.coll.find().sort( {"a":1} ).skip(count / 2 - 1).limit(1);
これは、人々が MySQLに提案 と同等です。
アグリゲートフレームワークを使用すると、ワンショットでそれを行うことができます。
並べ替え=>配列に入力並べ替え値=>配列のサイズを取得=>サイズを2で除算=>除算のInt値を取得(中央値の左側)=>左側(右側)に1を追加=>配列要素を取得左側と右側=> 2つの要素の平均
これは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);
そしてここで結果の集計:
{
"aggregate": "book" ,
"pipeline": [
{
"$group": {
"_id": "$owner" ,
"nbBooks": {
"$sum": 1
}
}
} , {
"$sort": {
"nbBooks": 1
}
} , {
"$group": {
"_id": null ,
"nbBooksArray": {
"$Push": "$nbBooks"
}
}
} , {
"$project": {
"nbBooksArray": 1 ,
"size": {
"$size": ["$nbBooksArray"]
}
}
} , {
"$project": {
"nbBooksArray": 1 ,
"middleFloat": {
"$divide": ["$size" , 2]
}
}
} , {
"$project": {
"middleFloat": 1 ,
"nbBooksArray": 1 ,
"beginMiddle": {
"$trunc": ["$middleFloat"]
}
}
} , {
"$project": {
"beginMiddle": 1 ,
"middleFloat": 1 ,
"nbBooksArray": 1 ,
"endMiddle": {
"$add": ["$beginMiddle" , 1]
}
}
} , {
"$project": {
"beginMiddle": 1 ,
"endMiddle": 1 ,
"middleFloat": 1 ,
"nbBooksArray": 1 ,
"beginValue": {
"$arrayElemAt": ["$nbBooksArray" , "$beginMiddle"]
} ,
"endValue": {
"$arrayElemAt": ["$nbBooksArray" , "$endMiddle"]
}
}
} , {
"$project": {
"beginMiddle": 1 ,
"endMiddle": 1 ,
"middleFloat": 1 ,
"nbBooksArray": 1 ,
"beginValue": 1 ,
"endValue": 1 ,
"median": {
"$avg": ["$beginValue" , "$endValue"]
}
}
}
]
}
maxiplayの答え は正確ではありませんが、それは私を正しい方向に導きました。与えられたソリューションの問題は、レコード数が偶数の場合にのみ機能することです。レコード数が奇数であるため、平均を計算することなく、中間点で値を取得する必要があります。
これが私がそれを機能させる方法です。
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"] }
}
}
}
}
])
集約フレームワークは、すぐに使用できる中央値をサポートしていません。だからあなたは自分で何かを書かなければならないでしょう。
これはアプリケーションレベルで行うことをお勧めします。通常のfind()を使用してすべてのドキュメントを取得し、結果セットを並べ替え(カーソルの.sort()
関数を使用するか、アプリケーションで並べ替えてデータベースで並べ替えます-決定)、要素size / 2
を取得します。
本当にデータベースレベルでそれを実行したい場合は、map-reduceを使用して実行できます。 map関数は、キーと単一の値(中央値を取得する値)を持つ配列を出力します。 reduce関数は、受け取った結果の配列を連結するだけなので、各キーはすべての値を持つ配列になります。次に、finalize関数は、配列を並べ替えて要素番号size / 2
を取得することにより、その配列の中央値を計算します。
開始Mongo 4.4
、$group
ステージに新しい集計演算子があります $accumulator
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
(accumulateArgs
)init
)b
アイテムを配列に蓄積します(accumulate
およびmerge
)b
アイテムの中央値計算を実行します(finalize
)