Иногда при использовании mapReduce требуется создать сквозную нумерацию записей, причём не хочется сначала гнать все записи на клиент, а потом обновлять их на сервере в отсортированном виде. Пример такой необходимости - создание таблицы рекордов какой-нибудь игры, когда надо отсортировать игроков по очкам и назначить каждому его место в таблице для последующей выборки. Но стантартная реализация mapReduce не позволяет такого сделать.
На помощь приходит JavaScript - будем хранить текущее значение счётчика непосредственно в данных объекта соединения.
Заметьте, что необходимо добавление функции finalize, иначе при повторном использовании данного соединения счётчик не обнулится.
f_map = function() { if (typeof db.top_idx != 'number') db.top_idx = 0; emit(this._id, { 'username': this.username, 'score': this.score, 'top_idx': db.top_idx++ }); } f_reduce = function(k, va) { return va[0]; } f_finalize = function(k, v) { db.top_idx = 0; return v; } db.runCommand({ 'mapreduce': 'Users', 'map': f_map, 'reduce': f_reduce, 'finalize': f_finalize, 'sort': { 'score': -1 }, 'out': 'Top' });
В новой версии счётчик можно обнулять непосредственно перед выполнением mapReduce, поэтому функция finalize становится не нужна.
f_map = function() { emit(this._id, { 'username': this.username, 'score': this.score, 'top_idx': db.top_idx++ }); } f_reduce = function(k, va) { return va[0]; } db.eval('db.top_idx = 0'); db.runCommand({ 'mapreduce': 'Users', 'map': f_map, 'reduce': f_reduce, 'sort': { 'score': -1 }, 'out': 'Top' });
Допустим, мы имеем коллекцию Users со следующими данными:
{ "_id": ObjectId("4bf6127031018ee4da1d404c"), "username": "Всегда Третий", "score": 114457 }
{ "_id": ObjectId("4bf6127331018ee4da1d404d"), "username": "Всегда Первый", "score": 234756 }
{ "_id": ObjectId("4bf6127031018ee4da1d404e"), "username": "Иногда Второй", "score": 123745 }
После выполнения запроса mapReduce получим такую коллекцию Top:
{ "_id": ObjectId("4bf6127331018ee4da1d404d"), "value": { "username": "Всегда Первый", "score": 234756, "top_idx": 1 } }
{ "_id": ObjectId("4bf6127031018ee4da1d404e"), "value": { "username": "Иногда Второй", "score": 123745, "top_idx": 2 } }
{ "_id": ObjectId("4bf6127031018ee4da1d404c"), "value": { "username": "Всегда Третий", "score": 114457, "top_idx": 3 } }
И получить позицию в таблице рекордов на какого-либо игрока легче лёгкого:
db.Top.find({ '_id': ObjectId("4bf6127031018ee4da1d404e") }, { 'top_idx': 1, 'score': 1 });
Дискуссия