у меня есть Test
база данных с коллекцией под названием collection
:
{
"_id": "576008e5b47a6120c800418d",
"UserID": "Paul",
"Page": "A"}
Я хочу записать веб-активность и использовать mapreduce, чтобы получить такой результат, как
{
"_id": "Paul",
"value": {
"A": 1,
"B": 0,
"C": 0,
"D": 0,
"E": 0
}
}
Для начала я попробовал простой код с PHP 7 MongoDB Driver 1.1.7 MapReduce, используя команду, которая не смогла декодировать документ с сервера:
<?php
$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");
$command = new MongoDB\Driver\Command(array(
"mapReduce" => "collection",
"map" => "function() { emit(this.UserID, 1); }",
"reduce" => "function(Users, Pages){".
"return Pages;}",
"out" => "ex"));
try {
$cursor = $manager->executeCommand('Test.collection', $command);
$response = $cursor->toArray()[0];
} catch(MongoDB\Driver\Exception $e) {
echo $e->getMessage(), "\n";
exit;
}
var_dump($response);
?>
Любые идеи будут оценены спасибо.
Не слишком уверенный, если бы я рекомендовал MapReduce для этого типа операций, сказал бы, что структура агрегации будет выполнять агрегацию с большей производительностью, поскольку все операции выполняются в собственном коде, не вызывая код для JavaScript для компиляции (в случае MapReduce).
С операцией агрегирования все, что вам нужно, это $group
трубопровод, который использует $cond
оператор, который позволяет преобразовать логическое условие в значение. В этом случае вы хотите указать pages
в качестве ключей и их количество в качестве значения, с документами, сгруппированными по UserID
,
Рассмотрите возможность выполнения следующей операции агрегирования в оболочке mongo:
db.collection.aggregate([
{
"$group": {
"_id": "$UserID",
"A": {
"$sum": {
"$cond": [
{ "$eq": [ "$Page", "A" ] },
1,
0
]
}
},
"B": {
"$sum": {
"$cond": [
{ "$eq": [ "$Page", "B" ] },
1,
0
]
}
},
"C": {
"$sum": {
"$cond": [
{ "$eq": [ "$Page", "C" ] },
1,
0
]
}
},
"D": {
"$sum": {
"$cond": [
{ "$eq": [ "$Page", "D" ] },
1,
0
]
}
},
"E": {
"$sum": {
"$cond": [
{ "$eq": [ "$Page", "E" ] },
1,
0
]
}
}
}
}
])
который будет производить вывод:
{
"_id": "Paul",
"A": 1,
"B": 0,
"C": 0,
"D": 0,
"E": 0
}
для приведенного выше образца документа.
Для краткости, если предположим, что у вас есть список страниц заранее, вы можете динамически создать конвейер следующим образом:
var groupOperation = { "$group": { "_id": "$UserID" } },
pages = ["A", "B", "C", "D", "E"];
pages.forEach(function (page){
groupOperation["$group"][page] = {
"$sum": {
"$cond": [
{ "$eq": [ "$Page", page ] },
1,
0
]
}
};
})
db.collection.aggregate([groupOperation]);
Теперь переводим это на PHP следующим образом:
<?php
$group_pipeline = [
'$group' => [
'_id' => '$UserID',
'A' => [
'$sum' => [
'$cond' => [ [ '$eq' => [ '$Page', 'A' ] ], 1, 0 ]
]
],
'B' => [
'$sum' => [
'$cond' => [ [ '$eq' => [ '$Page', 'B' ] ], 1, 0 ]
]
],
'C' => [
'$sum' => [
'$cond' => [ [ '$eq' => [ '$Page', 'C' ] ], 1, 0 ]
]
],
'D' => [
'$sum' => [
'$cond' => [ [ '$eq' => [ '$Page', 'D' ] ], 1, 0 ]
]
],
'E' => [
'$sum' => [
'$cond' => [ [ '$eq' => [ '$Page', 'E' ] ], 1, 0 ]
]
]
],
];
$aggregation = $collection->aggregate([ group_pipeline ]);
?>
Если вы предпочитаете использовать MapReduce, подумайте об изменении карты и уменьшите функции до:
db.collection.mapReduce(
function() {
var obj = {};
["A", "B", "C", "D", "E"].forEach(function (page){ obj[page] = 0; } );
obj[this.Page] = 1;
emit(this.UserID, obj);
},
function(key, values) {
var obj = {};
values.forEach(function(value) {
Object.keys(value).forEach(function(key) {
if (!obj.hasOwnProperty(key)){
obj[key] = 0;
}
obj[key]++;
});
});
return obj;
},
{ "out": { "inline": 1 } }
)
Который дает вывод:
{
"results" : [
{
"_id" : "Paul",
"value" : {
"A" : 1,
"B" : 0,
"C" : 0,
"D" : 0,
"E" : 0
}
}
]
}
Перевод приведенной выше операции mapReduce в PHP тривиален.
Других решений пока нет …