sql >> Database teknologi >  >> NoSQL >> MongoDB

mongoDB Aggregation:sum baseret på matrixnavne

Der er meget i dette, især hvis du er relativt ny til at bruge samlet , men det kan være færdig. Jeg vil forklare stadierne efter fortegnelsen:

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
        ]}}
    }}
])

Oversigt

Dette tager den antagelse, at "spillerne" i hver "sejr" og "tab"-array alle er unikke til at starte med. Det virkede rimeligt for det, der så ud til at være modelleret her:

  1. Slap af begge arrays. Dette skaber dubletter, men de vil blive fjernet senere.

  2. Ved projektering er der en vis brug af $cond operator (en ternær) for at få nogle bogstavelige strengværdier. Og den sidste brug er speciel, fordi og array bliver tilføjet. Så efter projicering vil arrayet blive afviklet igen. Flere dubletter, men det er meningen. Én "sejr", én "tab"-rekord for hver.

  3. Mere projektion med $cond operatør og brugen af ​​$eq også operatør. Denne gang fusionerer vi de to felter i ét. Så ved at bruge dette, når "type" af feltet matcher værdien i "score", så bruges det "nøglefelt" til "resultat" feltværdien. Resultatet er, at de to forskellige felter "vind" og "tab" deler nu samme navn, identificeret med "type".

  4. At slippe af med dubletterne i hvert dokument. Du skal blot gruppere efter dokumentet _id og "resultat"-felterne som nøgler. Nu skulle der være de samme "vind" og "tab"-poster, som der var i det originale dokument, bare i en anden form, efterhånden som de fjernes fra arrays.

  5. Grupper til sidst på tværs af alle dokumenter for at få totalerne pr. "spiller". Mere brug af $cond og $eq men denne gang for at afgøre, om det aktuelle dokument er en "sejr" eller et "tab". Så hvor dette matcher returnerer vi 1, og hvor falsk returnerer vi 0. Disse værdier sendes til $sum for at få det samlede antal "sejre" og "tab".

Og det forklarer, hvordan man kommer til resultatet.

Få mere at vide om aggregeringsoperatørerne fra dokumentationen. Nogle af de "sjove" brug for $cond i denne fortegnelse burde kunne erstattes med en $ bogstaveligt operatør. Men det vil ikke være tilgængeligt, før version 2.6 og nyere er udgivet.

"Simplified" case for MongoDB 2.6 og nyere

Selvfølgelig er der et nyt sæt operatører i hvad er den kommende udgivelse i skrivende stund, hvilket vil være med til at forenkle dette noget:

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
        ]}}
    }}

])

Men "forenklet" kan diskuteres. For mig "føles" det bare som om, det "tuder rundt" og gør mere arbejde. Det er bestemt mere traditionelt, da det simpelthen er afhængigt af $ setUnion for at flette array-resultaterne.

Men det "arbejde" ville blive annulleret ved at ændre dit skema lidt, som vist her:

{
    "_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
    "win": [
        {
            "player" : "Player2",
            "type" : "win"
        },
        {
            "player" : "Player4",
            "type" : "win"
        }
    ],
    "loss" : [
        {
            "player" : "Player6",
            "type" : "loss"
        },
        {
            "player" : "Player5",
            "type" : "loss"
        },
    ]
}

Og dette fjerner behovet for at projicere array-indholdet ved at tilføje "type"-attributten, som vi har gjort, og reducerer forespørgslen og det udførte arbejde:

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
        ]}}
    }}

])

Og selvfølgelig bare ændre dit skema som følger:

{
    "_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
    "results" : [
        {
            "player" : "Player6",
            "type" : "loss"
        },
        {
            "player" : "Player5",
            "type" : "loss"
        },
        {
            "player" : "Player2",
            "type" : "win"
        },
        {
            "player" : "Player4",
            "type" : "win"
        }
    ]
}

Det gør tingene meget let. Og dette kunne gøres i versioner før 2.6. Så du kunne gøre det lige nu:

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
        ]}}
    }}

])

Så for mig, hvis det var min ansøgning, ville jeg have skemaet i den sidste form vist ovenfor i stedet for det, du har. Alt det arbejde, der udføres i de leverede aggregeringsoperationer (med undtagelse af den sidste erklæring) er rettet mod at tage den eksisterende skemaform og manipulere den til denne form, så det er nemt at køre den simple aggregeringssætning som vist ovenfor.

Da hver spiller er "tagget" med "vind/tab"-attributten, kan du altid bare diskret få adgang til dine "vindere/løsere" alligevel.

Som en sidste ting. Din dato er en streng. Det kan jeg ikke lide.

Der kan have været en grund til at gøre det, men jeg kan ikke se det. Hvis du har brug for at gruppere efter dag det er nemt at gøre i aggregering blot ved at bruge en korrekt BSON-dato. Du vil også så nemt kunne arbejde med andre tidsintervaller.

Så hvis du har fastsat datoen og gjort den til startdato , og erstattede "varighed" med sluttid , så får du beholde noget, som du kan få "varigheden" fra ved simpel matematik + Du får masser af ekstra fordele ved at have disse som en datoværdi i stedet for.

Så det kan give dig lidt stof til eftertanke om dit skema.

For dem, der er interesseret, er her noget kode, jeg brugte til at generere et fungerende sæt data:

// 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);
}


  1. MongoDB .NET genererer ikke _id ved upsert

  2. Mongoose - forårsaget af ::11000 E11000 duplikatnøglefejlindeks?

  3. Opgradering af ældre mongo-database efter utilsigtet mongo-versionsopgradering

  4. Java-driverækvivalent til JavaScript-shell's Object.bsonsize( doc )?