MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

преброяване на масиви във всички документи с mongo

Лично аз не съм голям фен на преобразуването на "данни" като имена на ключове в резултат. Принципите на рамката за агрегиране са склонни да се съгласуват, тъй като този вид операции също не се поддържат.

Така че личното предпочитание е да поддържате „данните“ като „данни“ и да приемете, че обработеният изход всъщност е по-добър и по-логичен за последователен обектен дизайн:

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

Което дава резултат като този:

[
    {
            "_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
            }
        ]
    }
]

Така че първоначалната $group отчита се на "пол" и подрежда хобитата в масив от масиви. След това да ви денормализира $unwind два пъти, за да получите единични елементи, $group за да получите общите суми за хоби за всеки пол и накрая да прегрупирате масив за всеки пол самостоятелно.

Това са едни и същи данни, те имат последователна и органична структура, която е лесна за обработка, а MongoDB и рамката за агрегиране бяха доста доволни от производството на този изход.

Ако наистина трябва да преобразувате данните си в имена на ключове (и все пак ви препоръчвам да не го правите, тъй като не е добър модел, който да следвате в дизайна), тогава извършването на такава трансформация от крайното състояние е доста тривиално за обработка на клиентски код. Като основен пример за JavaScript, подходящ за обвивката:

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

И тогава вие основно обработвате всеки резултат от курсора в желаната изходна форма, която наистина не е функция за агрегиране, която така или иначе наистина се изисква на сървъра:

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

Където това също би трябвало да е доста несериозно, за да се приложи такъв вид манипулация в обработката на потока на резултата от курсора, за да се трансформира според изискванията, тъй като по същество това е същата логика.

От друга страна, винаги можете да приложите цялата манипулация на сървъра, като използвате mapReduce вместо това:

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

Където mapReduce има свой собствен различен стил на извеждане, но същите принципи се използват при натрупване и манипулиране, ако не е толкова ефективно, колкото може да направи рамката за агрегиране:

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

В крайна сметка все още казвам, че първата форма на обработка е най-ефективната и осигурява според мен най-естествената и последователна работа на изходните данни, без дори да се опитвам да преобразувам точките от данни в имената на ключовете. Вероятно е най-добре да обмислите да следвате този модел, но ако наистина трябва, тогава има начини за манипулиране на резултатите в желаната форма в различни подходи за обработка.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Лошо ли е да промените типа _id в MongoDB на цяло число?

  2. Обновяване/поправяне на Mongoose?

  3. най-добрата практика на обединяването на django + PyMongo?

  4. MongoError:неуспешно свързване със сървъра при първото свързване

  5. Как да съхранявате полето за дата като ISODate() с помощта на jackson в MongoDb