sql >> Databasteknik >  >> NoSQL >> MongoDB

räkna arrayförekomster över alla dokument med mongo

Personligen är jag inte ett stort fan av att omvandla "data" som namn på nycklar i ett resultat. Principerna för aggregeringsramverket tenderar att överensstämma eftersom denna typ av operation inte heller stöds.

Så den personliga preferensen är att behålla "data" som "data" och acceptera att den bearbetade utdata faktiskt är bättre och mer logisk för en konsekvent objektdesign:

db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
])

Vilket ger ett resultat som detta:

[
    {
            "_id" : "female",
            "total" : 1,
            "hobbies" : [
                {
                    "name" : "tennis",
                    "count" : 1
                },
                {
                    "name" : "football",
                    "count" : 1
                }
            ]
    },
    {
        "_id" : "male",
        "total" : 2,
        "hobbies" : [
            {
                "name" : "swimming",
                "count" : 1
            },
            {
                "name" : "tennis",
                "count" : 2
            },
            {
                "name" : "football",
                "count" : 2
            }
        ]
    }
]

Så den initiala $group räknar per "kön" och lägger upp hobbyerna i en rad matriser. För att sedan avnormalisera dig $unwind två gånger för att få enstaka objekt, $group för att få summan per hobby under varje kön och slutligen omgruppera en array för varje kön ensam.

Det är samma data, det har en konsekvent och organisk struktur som är lätt att bearbeta, och MongoDB och aggregeringsramverket var ganska nöjda med att producera denna produktion.

Om du verkligen måste konvertera dina data till namn på nycklar (och jag rekommenderar fortfarande att du inte gör det eftersom det inte är ett bra mönster att följa i designen), så är det ganska trivialt att göra en sådan omvandling från det slutliga tillståndet för klientkodsbehandling. Som ett grundläggande JavaScript-exempel lämpligt för skalet:

var out = db.people.aggregate([
    { "$group": {
        "_id": "$sex",
        "hobbies": { "$push": "$hobbies" },
        "total": { "$sum": 1 }
    }},
    { "$unwind": "$hobbies" },
    { "$unwind": "$hobbies" },
    { "$group": {
        "_id": {
            "sex": "$_id",
            "hobby": "$hobbies"
        },
        "total": { "$first": "$total" },
        "hobbyCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": "$_id.sex",
        "total": { "$first": "$total" },
        "hobbies": {
            "$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
        }
    }}
]).toArray();

out.forEach(function(doc) {
    var obj = {};
    doc.hobbies.sort(function(a,b) { return a.count < b.count });
    doc.hobbies.forEach(function(hobby) {
        obj[hobby.name] = hobby.count;
    });
    doc.hobbies = obj;
    printjson(doc);
});

Och sedan bearbetar du i princip varje markörresultat till den önskade utdataformen, vilket verkligen inte är en aggregeringsfunktion som verkligen krävs på servern ändå:

{
    "_id" : "female",
    "total" : 1,
    "hobbies" : {
        "tennis" : 1,
        "football" : 1
    }
}
{
    "_id" : "male",
    "total" : 2,
    "hobbies" : {
        "tennis" : 2,
        "football" : 2,
        "swimming" : 1
    }
}

Där det också borde vara ganska trival att implementera den sortens manipulation i strömbehandling av markörresultatet för att transformera efter behov, eftersom det i princip bara är samma logik.

Å andra sidan kan du alltid implementera all manipulation på servern med mapReduce istället:

db.people.mapReduce(
    function() {
        emit(
            this.sex,
            { 
                "total": 1,
                "hobbies": this.hobbies.map(function(key) {
                    return { "name": key, "count": 1 };
                })
            }
        );
    },
    function(key,values) {
        var obj  = {},
            reduced = {
                "total": 0,
                "hobbies": []
            };

        values.forEach(function(value) {
            reduced.total += value.total;
            value.hobbies.forEach(function(hobby) {
                if ( !obj.hasOwnProperty(hobby.name) )
                    obj[hobby.name] = 0;
                obj[hobby.name] += hobby.count;
            });
        });

        reduced.hobbies = Object.keys(obj).map(function(key) {
            return { "name": key, "count": obj[key] };
        }).sort(function(a,b) {
            return a.count < b.count;
        });

        return reduced;
    },
    { 
        "out": { "inline": 1 },
        "finalize": function(key,value) {
            var obj = {};
            value.hobbies.forEach(function(hobby) {
                obj[hobby.name] = hobby.count;
            });
            value.hobbies = obj;
            return value;
        }
    }
)

Där mapReduce har sin egen distinkta utmatningsstil, men samma principer används vid ackumulering och manipulation, om inte troligt så effektivt som aggregeringsramverket kan göra:

   "results" : [
        {
            "_id" : "female",
            "value" : {
                "total" : 1,
                "hobbies" : {
                    "football" : 1,
                    "tennis" : 1
                }
            }
        },
        {
            "_id" : "male",
            "value" : {
                "total" : 2,
                "hobbies" : {
                    "football" : 2,
                    "tennis" : 2,
                    "swimming" : 1
                }
            }
        }
    ]

I slutet av dagen säger jag fortfarande att den första formen av bearbetning är den mest effektiva och ger mig den mest naturliga och konsekventa bearbetningen av datautmatningen, utan att ens försöka konvertera datapunkterna till namn på nycklar. Det är förmodligen bäst att överväga att följa det mönstret, men om du verkligen måste, så finns det sätt att manipulera resultaten till en önskad form i olika metoder för bearbetning.




  1. Redis Config Set med Node jS

  2. sudo service mongodb omstart ger okänt servicefel i ubuntu 14.0.4

  3. MongoDB $push vs $addToSet:Vad är skillnaden?

  4. Hitta största dokumentstorlek i MongoDB