Сгруппируйте и возьмите K каждой группы с помощью структуры агрегации.

У меня есть коллекция MongoDB с документами в этом формате:

{ "_id":..., "Group": 1, "Value": 4 }
{ "_id":..., "Group": 2, "Value": 8 }
{ "_id":..., "Group": 1, "Value": 10 }

и так далее...

Учитывая X, Y и K, я хотел бы использовать структуру агрегации, чтобы сделать следующее:

  • Выберите все документы со свойством Group между X и Y
  • Сгруппировать по свойству Group
  • В каждой группе: выберите только K документов с самым большим свойством Value.

Любая идея о том, как это сделать?


person Flavien    schedule 14.07.2013    source источник


Ответы (2)


Первые два шага достаточно просты:

X = 1; Y = 3
db.collection.aggregate( [
    { $match: { Group: { $gte: X, $lt: Y } } },
    { $group: { _id: '$Group' } }
] );

С приведенным выше набором данных это дает вам:

{ "result" : [ { "_id" : 2 }, { "_id" : 1 } ], "ok" : 1 }

Чтобы иметь возможность выбрать K документов с наибольшим значением, вам нужно изменить свою группу, включив в нее каждый документ и максимальное значение для этой группы, а затем раскрутить, чтобы мы могли сортировать по группе и значению (DESC):

db.collection.aggregate( [
    { $match: { Group: { $gte: X, $lt: Y } } },
    { $group: { 
        _id: '$Group', 
        docs: { $push: { _id: '$_id', Group: '$Group', Value: '$Value' } }
    } },
    { $unwind: '$docs' },
    { $sort: { 'docs.Group': 1, 'docs.Value': -1 } }
] );

Однако с этого момента мы поражены тем, что в отличие от $push в качестве обычного оператора запроса, мы пока не можем выполнить $push + $slice в структуре агрегации. Единственное, что мы можем сделать, это еще одна группа, чтобы ваше приложение могло выбрать K документов в группе с наивысшими значениями:

db.collection.aggregate( [
    { $match: { Group: { $gte: X, $lt: Y } } },
    { $group: { 
        _id: '$Group', 
        docs: { $push: { _id: '$_id', Group: '$Group', Value: '$Value' } }
    } },
    { $unwind: '$docs' },
    { $sort: { 'docs.Group': 1, 'docs.Value': -1 } }
    { $group: {
        _id: '$docs.Group', 
        docs: { $push: { 
            _id: '$docs._id', 
            Group: '$docs.Group', 
            Value: '$docs.Value' 
        } } 
    } }
] );

Что затем выводит (после добавления еще нескольких документов):

{
    "result" : [
        {
            "_id" : 2,
            "docs" : [
                {
                    "_id" : ObjectId("51e3a73dea832e98dd545f68"),
                    "Group" : 2,
                    "Value" : 22
                },
                {
                    "_id" : ObjectId("51e3a738ea832e98dd545f66"),
                    "Group" : 2,
                    "Value" : 17
                },
                {
                    "_id" : ObjectId("51e3a73aea832e98dd545f67"),
                    "Group" : 2,
                    "Value" : 13
                },
                {
                    "_id" : ObjectId("51e3a2aaea832e98dd545f64"),
                    "Group" : 2,
                    "Value" : 8
                },
                {
                    "_id" : ObjectId("51e3a736ea832e98dd545f65"),
                    "Group" : 2,
                    "Value" : 7
                }
            ]
        },
        {
            "_id" : 1,
            "docs" : [
                {
                    "_id" : ObjectId("51e3a740ea832e98dd545f69"),
                    "Group" : 1,
                    "Value" : 21
                },
                {
                    "_id" : ObjectId("51e3a2a5ea832e98dd545f63"),
                    "Group" : 1,
                    "Value" : 10
                },
                {
                    "_id" : ObjectId("51e3a742ea832e98dd545f6a"),
                    "Group" : 1,
                    "Value" : 5
                },
                {
                    "_id" : ObjectId("51e3a2a3ea832e98dd545f62"),
                    "Group" : 1,
                    "Value" : 4
                },
                {
                    "_id" : ObjectId("51e3a745ea832e98dd545f6b"),
                    "Group" : 1,
                    "Value" : 2
                }
            ]
        }
    ],
    "ok" : 1
}

Обновление для MongoDB >= v3.2:

Теперь вы можете добавить этап $project в конец конвейера агрегации, чтобы ограничить количество элементов в группе:

$project: {
    _id: '$_id',
    docs: {
        $slice: [ 
            '$docs',
            3 // max number of elements returned from the start of the array
        ]
    } 
}
person Derick    schedule 15.07.2013
comment
Отличная идея. Слайс делает свое дело - person Asad Ali; 28.09.2017

взятый:

db.rec_log.aggregate( [
    { $match: { uid: { $in: [ "zxf-1", "zxf-2" ] } } },
    { $sort: { uid: 1, c_date: -1 } },
    { $group: { _id: '$uid', docs: { $push: { content: '$content' } } } },
    { $project: { _id: '$_id', docs: { $slice: [ '$docs', 2 ] } } }
] );

взять один:

db.rec_log.aggregate( [
    { $match: { uid: { $in: [ "zxf-1", "zxf-2" ] } } },
    { $sort: { uid: 1, c_date: -1 } },
    { $group: { _id: '$uid', docs: { $push: { content: '$content' } } } },
    { $project: { _id: '$_id', docs0: { $arrayElemAt: ["$docs", 0] } } },
    { $project: { _id: '$_id', latest_content: "$docs0.content" } }
] );

person zxf曾爷    schedule 04.12.2017