Introdução
As chaves usadas para o padrão Map-Reduce podem ser um destes três tipos:
- valor único: inteiro, flutuante, string …
- objeto: Date Object, NumberLong Object …
- conjunto de dados (documento): {índice a: 123, índice b: nova data (2015,0,1)}
Este tutorial mostra três exemplos para cada tipo para resolver o seguinte problema.
O problema
Vamos considerar o documento abaixo j4
>db.j4.find()
{ "_id" : 1, "value" : { "timeline" : ISODate("2015-07-01T01:23:03Z") } }
{ "_id" : 2, "value" : { "timeline" : ISODate("2015-07-01T05:00:00Z") } }
{ "_id" : 3, "value" : { "timeline" : ISODate("2015-07-03T13:02:14Z") } }
{ "_id" : 4, "value" : { "timeline" : ISODate("2015-07-03T20:10:06Z") } }
{ "_id" : 5, "value" : { "timeline" : ISODate("2015-07-03T21:03:07Z") } }
{ "_id" : 6, "value" : { } }
Aqui está o problema: conte os elementos de j4 agrupados por aaaa-mm-dd .
A saída deve ser semelhante a esta:
date : 2015-07-01, total : 2
date : 2015-07-03, total : 3
Solução 1: digite como string de data
Uma data pode ser tratada como string se não houver necessidade de fazer uma consulta complexa sobre ela. <br>
Esta solução transforma um objeto de data em uma string com preenchimento e usa a string como chave.
var map_use_pad = function() {
if (this.value.timeline == null) return;
// for this pad function, thanks to https://stackoverflow.com/users/182668/pointy
var pad = function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
};
var d = this.value.timeline;
var key = d.getFullYear() + "-" + pad(d.getMonth() + 1, 2, 0) + "-" + pad(d.getDate(), 2, 0);
emit(key, {"tot":1});
};
var reduce_with_pad = function (key, values) {
var reduce = {"tot" : 0};
/* if date as object is needed, use this instead */
// var reduce = {"date_as_object" : new Date(key + "T00:00:00.000Z"), "tot" : 0};
values.forEach(function(value){
reduce.tot += value.tot;
});
return reduce;
}
Agora é possível usar a função de redução de mapa sobre o documento j4 da seguinte maneira:
>db.j4.mapReduce(map_use_pad, reduce_with_pad, {out : {reduce: 'out_pad' }});
{
"result" : "out_pad",
"timeMillis" : 81,
"counts" : {
"input" : 6,
"emit" : 5,
"reduce" : 2,
"output" : 2
},
"ok" : 1
}
Vamos dar uma olhada no resultado:
> db.out_pad.find()
{ "_id" : "2015-07-01", "value" : { "tot" : 2 } }
{ "_id" : "2015-07-03", "value" : { "tot" : 3 } }
Solução 2: chave como objeto de data
Uma vez resolvido o problema e gerado o documento, pode ser necessário operar consultas sobre a data (_id) como o seguinte (pseudo-código):
db.out_pad.find({_id > "2015-01-01" AND _id < "2015-07-02"}) W R O N G!!!
Contanto que _id seja uma string, apenas expressões regulares complexas podem ser usadas para fazer o pseudocódigo.
Esta segunda solução resolve o problema: a chave (_id) é um objeto de data.
var map_use_object = function() {
if (this.value.timeline == null) return;
var d = this.value.timeline;
var key = new Date( Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) );
emit(key, {"tot":1});
};
var reduce_with_object = function (key, values) {
var reduce = {"tot" : 0};
values.forEach(function(value){
reduce.tot += value.tot;
});
return reduce;
}
Uma vez gerado o resultado usando …
db.j4.mapReduce(map_use_object, reduce_with_object, {out : {reduce: 'out_object' }});
… é possível realizar a consulta esperada:
>db.out_object.find
(
{
_id:{
$gt: new Date("2015-01-01T00:00:00Z"),
$lt: new Date("2015-07-02T00:00:00Z")
}
}
)
{ "_id" : ISODate("2015-07-01T00:00:00Z"), "value" : { "tot" : 2 } }
Solução 3: chave como documento
A maneira mais fácil de dividir uma data em um grupo exclusivo de chaves é algo assim:
chave = {ano: aaaa, mês: mm, dia: dd}
Esta chave permite a construção fácil de certos tipos de consultas.
var map_use_set = function() {
if (this.value.timeline == null) return;
var d = this.value.timeline;
var key = { year:d.getFullYear(), month:d.getMonth()+1, day:d.getDate() };
emit(key, {"tot":1});
};
var reduce_with_set = function (key, values) {
var reduce = {"tot" : 0};
values.forEach(function(value){
reduce.tot += value.tot;
});
return reduce;
}
Uma vez gerado o resultado usando …
db.j4.mapReduce(map_use_set, reduce_with_set, {out : {reduce: 'out_set' }});
… é possível saber o total para cada primeiro dia de 2015 com uma simples consulta:
> db.out_set.find({"_id.year":2015, "_id.day" : 1})
{ "_id" : { "year" : 2015, "month" : 7, "day" : 1 }, "value" : { "tot" : 2 } }
Conclusão
A escolha da chave de redução de mapa tem uma consequência importante do tipo de consultas que estão disponíveis depois que os dados são gerados. É a natureza das consultas que impõe o tipo de chave a ser usada.