how to use mapreduce in mongoose/mongodb query sub

2020-06-05 01:45发布

问题:

I implemented a simple message system in mongoose/mongodb, the schema is like the following

var schema = new mongoose.Schema({
    user: {type:String, required:true},
    updated: {type:Date, default:new Date()},       
    msgs: [ {m:String, // message itself 
             d:Date,   // date of message
             s: String,  // message sender
             r:Boolean   // read or not
            } ],
});

all the messages are stored in msg nested array, now I want to query the messages from certain sender, for example,

{
  "_id" : ObjectId("52c7cbe6d72ecb07f9bbc148"),
  'user':'abc'
  "msgs" : [{
      "m" : "I want to meet you",
      "d" : new Date("4/1/2014 08:52:54"),
      "s" : "user1",
      "r" : false,
      "_id" : ObjectId("52c7cbe69d09f89025000005")
    }, {
      "m" : "I want to meet you",
      "d" : new Date("4/1/2014 08:52:56"),
      "s" : "user1",
      "r" : false,
      "_id" : ObjectId("52c7cbe89d09f89025000006")
    }, {
      "m" : "I want to meet you",
      "d" : new Date("4/1/2014 08:52:58"),
      "s" : "user2",
      "r" : false,
      "_id" : ObjectId("52c7cbea9d09f89025000007")
    }
   }

Here I have a document for user 'aa' who has three messages, two messages are from 'user1' and one message is from 'user2'. And I want to query for the messages from 'user1'

Basically there are two ways to do it, map-reduce or aggregate. I tried the map-reduce solution.

var o = {}; 
o.map = function() { 
    this.msgs.forEach(function(msg){ 
        if(msg.s == person){  emit( msg.s, {m:msg.m,d:msg.d,r:msg.r}); }
    })
}       
o.reduce = function(key, values) {
    var msgs = [];
    for(var i=0;i<values.length;i++)
    msgs.push(values[i]);       
    return JSON.stringify(msgs);
}
o.query  = {user:'username'};  
o.scope = {person:'user1'};
model.mapReduce(o,function (err, data, stats) { 
    console.log('map reduce took %d ms', stats.processtime)
    if(err) callback(err);
    else callback(null,data);
})

Ultimately, it works with results like

 [ 
    { _id: 'helxsz',
    value: '[
        {"m":"I want to meet you","d":"2014-01-04T08:52:54.112Z","r":false}, ....
        ]
 ]

The result is what I want, but the format is a bit complex. How can I change to make output the format like this

    { sender: 'helxsz',
      messages: '[
        {"m":"I want to meet you","d":"2014-01-04T08:52:54.112Z","r":false}, ...
        ]
    }

and how I sort and limit the results, so I have to manually do it the reduce function?

and one last the map reduce methods takes 28 ms to query the result, for the simulation, my collection has three documents, each document has a msg array of 4 subdocument. for me , 28 ms is a bit of too much for the query, is it , now I also indexed on the 'user' field.

回答1:

I am not sure how efficient it is for you, but for formatting this will work like below I made custom key names title, className and start which are not in the collection. So store the result of mapReduce in a new collection and retrive it. (if you are not going to run mapReduce on every single request)

  db.events.aggregate([{
       $project: {
        title: "$value",
        className: "$_id.method",
        start: "$_id.time",
        _id:0 }
   }]
)


回答2:

If you use the map-reduce framework, which I do not recommend due to its performance, then you can use the finalize function together with the map and reduce to reshape the final result or alternatively, rename the fields during in the emit functions.

Instead I recommend using the aggregation framework which has much better performance:

db.collection.aggregate([
    {$match: {"user" : "user1"}},
    {$project: {"_id": 0, "sender": "$user", "messages": "$msgs"}}
])


回答3:

Where you say,

emit( msg.s, {m:msg.m,d:msg.d,r:msg.r});

Instead say:

 emit( sender: msg.s, messages: {m:msg.m,d:msg.d,r:msg.r});