В това има много, особено ако сте сравнително нов в използването на агрегат , но тоса бъде направено. Ще обясня етапите след списъка:
db.collection.aggregate([
// 1. Unwind both arrays
{"$unwind": "$win"},
{"$unwind": "$loss"},
// 2. Cast each field with a type and the array on the end
{"$project":{
"win.player": "$win.player",
"win.type": {"$cond":[1,"win",0]},
"loss.player": "$loss.player",
"loss.type": {"$cond": [1,"loss",0]},
"score": {"$cond":[1,["win", "loss"],0]}
}},
// Unwind the "score" array
{"$unwind": "$score"},
// 3. Reshape to "result" based on the value of "score"
{"$project": {
"result.player": {"$cond": [
{"$eq": ["$win.type","$score"]},
"$win.player",
"$loss.player"
] },
"result.type": {"$cond": [
{"$eq":["$win.type", "$score"]},
"$win.type",
"$loss.type"
]}
}},
// 4. Get all unique result within each document
{"$group": { "_id": { "_id":"$_id", "result": "$result" } }},
// 5. Sum wins and losses across documents
{"$group": {
"_id": "$_id.result.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$_id.result.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$_id.result.type","loss"]},1,0
]}}
}}
])
Резюме
Това наистина приема предположението, че „играчите“ във всеки масив „победа“ и „загуба“ са уникални като начало. Това изглеждаше разумно за това, което изглеждаше моделирано тук:
-
Развийте двата масива. Това създава дубликати, но те ще бъдат премахнати по-късно.
-
При проектиране има известно използване на $cond оператор (троичен), за да получите някои буквални низови стойности. И последното използване е специално, защото се добавя масив. Така че след проектирането този масив ще се развие отново. Още дубликати, но това е смисълът. Една "победа", една "загуба" запис за всеки.
-
Повече проекция с $cond оператор и използването на $eq оператор също. Този път сливаме двете полета в едно. Използвайки това, когато „типът“ на полето съвпада със стойността в „резултат“, тогава това „ключово поле“ се използва за стойността на полето „резултат“. Резултатът е, че двете различни полета „победа“ и „загуба“ сега споделят едно и също име, идентифицирани чрез „тип“.
-
Отървете се от дубликатите във всеки документ. Просто групиране по документа
_id
и полетата "резултат" като ключове. Сега трябва да има същите записи "печалба" и "загуба", както в оригиналния документ, само в различна форма, тъй като са премахнати от масивите. -
Накрая групирайте всички документи, за да получите общите суми за "играч". Повече използване на $cond и $eq но този път, за да определи дали текущият документ е "печалба" или "загуба". Така че, когато това съвпада, връщаме 1 и където false, връщаме 0. Тези стойности се предават на $sum за да получите общия брой "победи" и "загуби".
И това обяснява как да стигнете до резултата.
Научете повече за операторите за агрегиране от документацията. Някои от „забавните“ употреби на $cond в този списък трябва да може да бъде заменен с $ литерал оператор. Но това няма да бъде налично до пускането на версия 2.6 и по-нови.
„Опростен“ случай за MongoDB 2.6 и по-нови версии
Разбира се, има нов набор оператори в това, което е предстоящото издание към момента на писане, което ще помогне да се опрости това донякъде:
db.collection.aggregate([
{ "$unwind": "$win" },
{ "$project": {
"win.player": "$win.player",
"win.type": { "$literal": "win" },
"loss": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id",
"loss": "$loss"
},
"win": { "$push": "$win" }
}},
{ "$unwind": "$_id.loss" },
{ "$project": {
"loss.player": "$_id.loss.player",
"loss.type": { "$literal": "loss" },
"win": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id._id",
"win": "$win"
},
"loss": { "$push": "$loss" }
}},
{ "$project": {
"_id": "$_id._id",
"results": { "$setUnion": [ "$_id.win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
Но "опростено" е спорно. За мен това просто се „чувства“, сякаш „мърмори“ и върши повече работа. Със сигурност е по-традиционен, тъй като просто разчита на $ setUnion за обединяване резултатите от масива.
Но тази „работа“ ще бъде анулирана, ако промените малко вашата схема, както е показано тук:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"win": [
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
],
"loss" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
]
}
И това премахва необходимостта от проектиране на съдържанието на масива чрез добавяне на атрибута "тип", както правехме досега, и намалява заявката и свършената работа:
db.collection.aggregate([
{ "$project": {
"results": { "$setUnion": [ "$win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
И, разбира се, просто променяте вашата схема, както следва:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"results" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
]
}
Това прави нещатамного лесно. И това може да се направи във версии преди 2.6. Така че можете да го направите точно сега:
db.collection.aggregate([
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
Така че за мен, ако беше моето приложение, щях да искам схемата в последния формуляр, показан по-горе, а не това, което имате. Цялата работа, извършена в предоставените операции за агрегиране (с изключение на последния израз), е насочена към вземане на съществуващата форма на схема и манипулирането й в това форма, така че след това е лесно да се изпълни простият оператор за агрегиране, както е показано по-горе.
Тъй като всеки играч е "маркиран" с атрибута "победа/загуба", вие така или иначе винаги можете просто да получите дискретен достъп до своите "победители/губещи".
Като за финал. Вашата дата е низ. Това не ми харесва.
Може да е имало причина за това, но не я виждам. Ако трябва групиране подени това е лесно да се направи при агрегиране само чрез използване на подходяща BSON дата. След това ще можете лесно да работите и с други времеви интервали.
Така че, ако сте коригирали датата и сте я направили начална_дата и замени „продължителност“ с крайно_време , тогава можете да запазите нещо, от което можете да получите „продължителността“ чрез проста математика + Получавате много допълнителни ползи, като вместо това ги има като стойност на дата.
Така че това може да ви даде храна за размисъл относно вашата схема.
За тези, които се интересуват, ето код, който използвах за генериране на работещ набор от данни:
// Ye-olde array shuffle
function shuffle(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
for ( var l=0; l<10000; l++ ) {
var players = ["Player1","Player2","Player3","Player4"];
var playlist = shuffle(players);
for ( var x=0; x<playlist.length; x++ ) {
var obj = {
player: playlist[x],
score: Math.floor(Math.random() * (100000 - 50 + 1)) +50
};
playlist[x] = obj;
}
var rec = {
duration: Math.floor(Math.random() * (50000 - 15000 +1)) +15000,
date: new Date(),
win: playlist.slice(0,2),
loss: playlist.slice(2)
};
db.game.insert(rec);
}