sql >> Databáze >  >> NoSQL >> MongoDB

mongoDB Agregace:součet na základě názvů polí

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:

  1. Rozviňte obě pole. Tím se vytvoří duplikáty, které však budou později odstraněny.

  2. 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.

  3. 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“.

  4. 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í.

  5. 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);
}


  1. Docker:Propojení kontejneru Spring Boot s kontejnerem Mongo DB

  2. MongoDB agregovaný počet položek ve dvou polích v různých dokumentech?

  3. Nelze získat připojení Jedis; Nelze získat zdroj z fondu

  4. Atomicita, izolace a souběžnost v MongoDB