MongoDB MapReduce, возврат только при подсчете > 1

У меня есть данные в MongoDB. Структура одного объекта такова:

{
    "_id" : ObjectId("5395177980a6b1ccf916312c"),
    "institutionId" : "831",
    "currentObject" : {
          "systemIdentifiers" : [
            {
                "value" : "24387",
                "system" : "ABC"
            }]
      }
}

Я должен знать, сколько объектов имеют одинаковые institutionId и systemIdentifiers[0].value и хочу вернуть только те, которые дублируются таким образом. Для этого я группирую их по этим идентификаторам и подсчитываю вхождения.

Объект (пара идентификаторов) должен быть возвращен, когда count больше 1.

Это кусок кода, который выполняет группировку с использованием MapReduce.

var map = function() {
    var key = this.institutionId;
    var val = this.currentObject.systemIdentifiers[0].value;
    emit({"institutionId":key,"workId":val}, {count:1});     
};
var reduce = function(key, values) {
    var count = 0;
    values.forEach(function(v) {
        count += v['count'];
    });
    return {count: count};
}
db.name.mapReduce(map, reduce, {out: "grouped"})
db.grouped.find()

Чтобы получить только те, у которых счет больше 1, я делаю

db.grouped.aggregate([{$match:{"value.count":{$gt: 1}}}])

Затем приводится пример результата

{
    "_id" : {
        "institutionId" : "1004",
        "workId" : "591426"
    },
    "value" : {
        "count" : 2
    }
}

Но мне любопытно, возможно ли это сделать, просто выполнив MapReduce как одно выражение. Например, добавить финализатор или что-то в этом роде.


person Szymon Roziewski    schedule 01.12.2016    source источник


Ответы (2)


Гораздо лучше, проще и эффективнее было бы использовать структуру агрегации, где вы можете использовать такие операторы, как $arrayElemAt для возврата первого вложенного документа из массива, а затем с помощью $group для агрегирования счетчиков. Затем вы можете поместить $match для фильтрации результатов по заданным критериям.

В следующем примере показан этот более быстрый подход:

db.name.aggregate([
    {
        "$project": {
            "key": "$institutionId",
            "val": {
                "$arrayElemAt": ["$currentObject.systemIdentifiers", 0]
            }
        }
    },
    {
        "$group": {
            "_id": {
                "institutionId": "$key",
                "workId": "$val.value"
            },
            "count": { "$sum": 1 }
        }
    },
    { "$match": { "count": { "$gt": 1 } } }
])
person chridam    schedule 06.12.2016

Если есть один документ с ключом, он никогда не попадет внутрь сокращения, считается уже уменьшенным, то есть поведение сокращения карты MongoDB:

MongoDB не будет вызывать функцию сокращения для ключ, который имеет только одно значение.

Использование finalzie также мало помогает, т.е. если в функции finalize вы делаете if count > 1 then return reducedVal else None, то в результате у вас будет None (вместо 1).

Я боюсь, что при использовании (одного) map-reduce документы, имеющие количество 1, будут всегда в результате, поскольку они запускаются из карты.

Вы можете использовать 2 операции уменьшения карты в цепочке, на второй карте вы не выдаете документы, имеющие количество ‹ 2. Но они не думают, что это лучше, чем дополнительный запрос, как в вашем примере.

person sergiuz    schedule 01.12.2016