Je toho hodně, zvláště pokud jste relativně nováčci v používání agregovat , ale může být hotov. Vysvětlím fáze po výpisu:
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
]}}
}}
])
Shrnutí
To vychází z předpokladu, že „hráči“ v každém poli „výher“ a „prohry“ jsou pro začátek jedineční. To se zdálo rozumné pro to, co se zdálo být modelováno zde:
-
Rozviňte obě pole. Tím se vytvoří duplikáty, které však budou později odstraněny.
-
Při promítání se do určité míry používá $cond operátor (ternární) za účelem získání doslovných řetězcových hodnot. A poslední použití je speciální, protože a pole se přidává. Takže po projekci se pole znovu odvine. Více duplikátů, ale o to jde. Jedna „výhra“, jedna „prohra“ pro každého.
-
Více projekce s $cond operátor a použití $eq také operátor. Tentokrát se slučujeme dvě pole do jednoho. Takže když použijete toto, když "typ" pole odpovídá hodnotě v "skóre", pak se toto "klíčové pole" použije pro hodnotu pole "výsledek". Výsledek je, že dvě různá pole „výhra“ a „prohra“ nyní sdílejí stejný název a jsou identifikovány podle „typu“.
-
Zbavte se duplikátů v každém dokumentu. Jednoduše seskupení podle dokumentu
_id
a pole "výsledek" jako klíče. Nyní by měly existovat stejné záznamy „výhra“ a „prohra“ jako v původním dokumentu, jen v jiné podobě, jak jsou odstraněny z polí. -
Nakonec seskupte všechny dokumenty, abyste získali součty na "hráče". Více využití $cond a $eq ale tentokrát k určení, zda je aktuální dokument „výhra“ nebo „prohra“. Takže tam, kde to odpovídá, vrátíme 1 a kde false vrátíme 0. Tyto hodnoty jsou předány do součet $ abyste získali celkový počet „výher“ a „proher“.
A to vysvětluje, jak se dostat k výsledku.
Další informace o operátorech agregace z dokumentace. Některé ze „zábavných“ použití pro $cond v tomto seznamu by mělo být možné nahradit $ doslovný operátor. To však nebude k dispozici, dokud nebude vydána verze 2.6 a vyšší.
"Zjednodušené" pouzdro pro MongoDB 2.6 a vyšší
Samozřejmostí je nová množina operátorů v jakém je nadcházející vydání v době psaní tohoto článku, což pomůže toto poněkud zjednodušit:
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
]}}
}}
])
Ale "zjednodušené" je diskutabilní. Mně to prostě "připadá", jako by to "chcíplo" a dělalo víc práce. Rozhodně je tradičnější, protože se jednoduše spoléhá na $ setUnion sloučit výsledky pole.
Ale tato "práce" by byla zrušena malou změnou vašeho schématu, jak je znázorněno zde:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"win": [
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
],
"loss" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
]
}
A to odstraňuje potřebu promítat obsah pole přidáním atributu "type" tak, jak jsme to dělali my, a redukuje to dotaz a vykonanou práci:
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
]}}
}}
])
A samozřejmě stačí změnit schéma následovně:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"results" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
]
}
Díky tomu jsou věci velmi snadný. A to by mohlo být provedeno ve verzích před 2.6. Takže to můžete udělat hned teď:
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
]}}
}}
])
Takže pro mě, pokud by to byla moje aplikace, chtěl bych schéma v poslední formě uvedené výše spíše než to, co máte. Veškerá práce provedená v dodaných agregačních operacích (s výjimkou posledního příkazu) je zaměřena na převzetí existující formy schématu a její manipulaci do toto formulář, takže je snadné spustit jednoduchý příkaz agregace, jak je uvedeno výše.
Protože je každý hráč „označen“ atributem „výhra/prohra“, máte vždy diskrétní přístup ke svým „vítězům/poraženým“.
Jako poslední věc. Vaše datum je řetězec. To se mi nelíbí.
Možná k tomu byl důvod, ale já ho nevidím. Pokud potřebujete seskupit podle dne to lze v agregaci snadno provést pouze pomocí správného data BSON. Také pak budete moci snadno pracovat s jinými časovými intervaly.
Pokud jste tedy stanovili datum a udělali z něj počáteční_datum a výraz „trvání“ byl nahrazen výrazem end_time , pak si necháte něco, z čeho můžete získat „dobu trvání“ jednoduchou matematikou + získáte spoustu navíc výhody tím, že je místo toho použije jako hodnotu data.
Takže to vám může dát podnět k zamyšlení nad vaším schématem.
Pro ty, které to zajímá, zde je nějaký kód, který jsem použil ke generování pracovní sady dat:
// 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);
}