Você pode realmente passar um objeto arbitrário no segundo parâmetro da chamada de emissão. Isso significa que você pode aproveitar isso e armazenar o ID do usuário nele. Por exemplo, sua função de mapa pode ser assim:
var mapFunc = function() {
if (this.track_redirect) {
var tempDoc = {};
tempDoc[this.track_userid] = 1;
emit(this.track_redirect, {
users_clicked: tempDoc,
total_clicks: 1
});
}
};
E sua função de redução pode ficar assim:
var reduceFunc = function(key, values) {
var summary = {
users_clicked: {},
total_clicks: 0
};
values.forEach(function (doc) {
summary.total_clicks += doc.total_clicks;
// Merge the properties of 2 objects together
// (and these are actually the userids)
Object.extend(summary.users_clicked, doc.users_clicked);
});
return summary;
};
A propriedade users_clicked do objeto de resumo basicamente armazena o id de cada usuário como uma propriedade (já que você não pode ter propriedades duplicadas, você pode garantir que ele armazenará usuários únicos). Observe também que você deve ter cuidado com o fato de que alguns dos valores passados para a função de redução podem ser resultado de uma redução anterior e o código de exemplo acima leva isso em consideração. Você pode encontrar mais sobre o referido comportamento nos documentos aqui .
Para obter a contagem exclusiva, você pode passar a função finalizadora que é chamada quando a fase de redução é concluída:
var finalFunc = function(key, value) {
// Counts the keys of an object. Taken from:
// http://stackoverflow.com/questions/18912/how-to-find-keys-of-a-hash
var countKeys = function(obj) {
var count = 0;
for(var i in obj) {
if (obj.hasOwnProperty(i))
{
count++;
}
}
return count;
};
return {
redirect: key,
total_clicks: value.total_clicks,
unique_clicks: countKeys(value.users_clicked)
};
};
Por fim, você pode executar o trabalho de redução do mapa assim (modifique o atributo out para atender às suas necessidades):
db.users.mapReduce(mapFunc, reduceFunc, { finalize: finalFunc, out: { inline: 1 }});