Я это делал на реальном примере по одному проекту - нужно обработать данные по пользовательским покупкам, которые хранятся в монге. Нас интересует два поля: ID пользователя и сумма покупки. В качестве ID пользователя выступает GUID.
Нужно сделать следующий отчет: показать пользователей с наибольшим количеством потраченных денег.
Если бы мы были в SQL, то табличка с данными о покупках была бы следующие:
CREATE TABLE sales( guid varchar(32) NOT NULL PRIMARY KEY, price float NOT NULL );Запрос, который выводит данный отчет, был бы следующий:
SELECT guid, sum(price) AS summa FROM sales GROUP BY guid ORDER BY summa DESC;В монге данные хранятся в коллекции stat, вид данных следующий:
> db.stat.findOne(); { "_id" : ObjectId("4e4c73eb41e5c790a7391848"), "guid" : "346fbb3968c9453d9dc8f8ebe0fa6763", "item" : "Apple MacBook Air MC968LL/A 11.6-Inch", "price" : 78.64, "retailerId" : "COST", "ip" : "0:0:0:0:0:0:0:1", "datetime" : "2011-07-28 10:12:38", "timezone" : "-1", "createDate" : "Thu Aug 18 2011 06:07:39 GMT+0400 (MSK)", }Что ж, приступим в мап-редьюсу...
function salesMap() { emit(this.guid, {price: this.price }); } function salesReduce(key, values) { var result = { count: 0, summa: 0.0 }; values.forEach(function(v){ if (v.price) { result.summa += v.price; result.count++; } }); return result; } db.userrep.drop(); db.stat.mapReduce(salesMap,salesReduce,{out:'userrep', verbose: true}); db.userrep.find();Функция emit(key, value), которую мы использовали в функции salesMap, подает на вход редьюсу пару ключ - значение. Ключом мы выбрали GUID, т.к. по нему идет группировка, а в значение кладем цену товара. Функция salesReduce получает на вход набор объектов, сгруппированных по ключу.
Внутри функции все тривиально:
1) создаем объект с результатами, пока что содержащий нулевые значения var result = { count: 0, summa: 0.0 };
2) пробегаемся по коллекции и заполняем результат
3) возвращаем результат.
4) PROFIT!!!
Вид результатов следующий:
> db.userrep.find().limit(5) { "_id" : "01dc299862094450ac232d384d883a5f", "value" : { "count" : 2, "summa" : 245.19 } } { "_id" : "0a1afef3b8b27942d9a8d02903ca2c28", "value" : { "count" : 1, "summa" : 30.43 } } { "_id" : "0c9d6a05458313b85706548c290991e9", "value" : { "count" : 0, "summa" : 0 } } { "_id" : "0f4e595202884408ae4c4c6306d764f9", "value" : { "count" : 1, "summa" : 88.43 } } { "_id" : "17a57229a9c14b2cb46337a3d196051c", "value" : { "count" : 1, "summa" : 49.54 } }Какая жалость - данные не отсортированы.
Монго не умеет сортировать коллекцию данным из составных объектов, т.е. мы не можем написать что-то вроде:
db.userrep.find().sort({value.summa: -1});Поэтому придется немножко извратиться:
db.userrep.find().forEach(function(v) { var s = v.value.summa; var c = v.value.count; var id = v._id; db.userrep.update({_id: id},{$set:{summa:s, count: c}}, true, true); });Мы просто скопировали поля summa, count из объекта value в объект-контейнер коллекции. Теперь данные выглядят так:
> db.userrep.find().limit(5); { "_id" : "01dc299862094450ac232d384d883a5f", "count" : 2, "summa" : 245.19, "value" : { "count" : 2, "summa" : 245.19 } } { "_id" : "0a1afef3b8b27942d9a8d02903ca2c28", "count" : 1, "summa" : 30.43, "value" : { "count" : 1, "summa" : 30.43 } } { "_id" : "0c9d6a05458313b85706548c290991e9", "count" : 0, "summa" : 0, "value" : { "count" : 0, "summa" : 0 } } { "_id" : "0f4e595202884408ae4c4c6306d764f9", "count" : 1, "summa" : 88.43, "value" : { "count" : 1, "summa" : 88.43 } } { "_id" : "17a57229a9c14b2cb46337a3d196051c", "count" : 1, "summa" : 49.54, "value" : { "count" : 1, "summa" : 49.54 } }Теперь, наконец, можно получить отсортированные данные:
> db.userrep.find().sort({summa:-1}).limit(5); { "_id" : "cb1332aed05d48fd9c51d0c5be584156", "count" : 4, "summa" : 496.72999999999996, "value" : { "count" : 4, "summa" : 496.72999999999996 } } { "_id" : "ade1efffe9d3fb116f33c320bf9b447e", "count" : 3, "summa" : 412.45000000000005, "value" : { "count" : 3, "summa" : 412.45000000000005 } } { "_id" : "ef98c2c430c4486394bc2b85ec5b1a77", "count" : 3, "summa" : 408.5, "value" : { "count" : 3, "summa" : 408.5 } } { "_id" : "5295f9b927c59dacdd3fb20d8173a19b", "count" : 4, "summa" : 367.08, "value" : { "count" : 4, "summa" : 367.08 } } { "_id" : "01dc299862094450ac232d384d883a5f", "count" : 2, "summa" : 245.19, "value" : { "count" : 2, "summa" : 245.19 } }
Комментариев нет:
Отправить комментарий