Help sorting documents by value?

Hello! I’m quite new Realm and having a great time with it, but I recently got stuck converting a piece of code from the C# drivers into Realm Functions code.

My goal is to get X amount of documents from a leaderboards collection, sorting them by an “experience” value inside the document (think top 100 players in a certain skill for example). This is how I did with the C# drivers:

private static async Task<List<LeaderboardPlayerEntry>> ListEntriesByExperience(Skill skill, int limit) {
            var database = MongoDatabase.GlobalMongoClient.GetDatabase("leaderboards");
            var collection = database.GetCollection<LeaderboardEntryDocument>("entries");

            return await collection
                        .Aggregate()
                        .Project(p => new LeaderboardPlayerEntry() { Name = p._id, Skill = skill, Value = p.Experience })
                        .SortByDescending(s => s.Value)
                        .Limit(limit)
                        .ToListAsync();
        }

Any pointers on how the same could be accomplished in Functions? Thanks!

Hi @TemeS,

You have an example of aggregation pipeline here:

And a function example at the top.

Does that help?

Cheers,
Maxime.

Thanks! I was able to get it working eventually.

Here’s the base code in case someone in the future needs it:

// arg = skill, arg1 = min range, arg2 = max range
exports = function(arg, arg1, arg2) {
  var collection = context.services.get("mongodb-atlas").db("dbName").collection("collectionName");
  
  let query = {}
  let sort = {}
  query[arg] = { "$gte": 1000}
  sort[arg] = -1
  
  return collection.find(query)
      .sort(sort)
      .toArray()
      .then(items => {
        var entries = items.slice(arg1, arg2).map(function(x) { 
          var result = { "Name": x._id, "Rank" : items.indexOf(x) + 1, "Experience": x[arg] };
          
          return result;
        });
        
        return entries;
      });
};

The object initialization for the query is a bit funky since I want the queried fields to be passed in as arguments instead of having them hardcoded in the function.

If anyone has any improvement suggestions I’ll gladly take those as well. New to both Javascript and Mongo Realm so I’m sure the current code is less than optimal :smile:

I think you could optimize the query by returning less documents from MongoDB.

Let’s imagine you have 100 docs in MDB. You are returning 100 docs and then you are slicing the results to only returns docs 50 to 60 (arg1 & 2).

I would add a .skip(arg1).limit(arg2-arg1) to avoid returning 10 times 100 docs and only return 10 each time.

Example:

exports = function(arg){
    const collection = context.services.get("mongodb-atlas").db("test").collection("messages");
    collection.find({}, {_id:1}).skip(5).limit(10).toArray().then((docs) => {
      console.log(docs.length);
      console.log(EJSON.stringify(docs));
    });
};

Cheers,
Maxime.

1 Like

Thanks a lot for the tips!

This looking alright to you now?

exports = function(field, arg1, arg2) {
  
  if (arg1 >= arg2) {
    console.error("MinRange is same or higher than MaxRange!");
    return;
  }
  
  var collection = context.services.get("mongodb-atlas").db("db").collection("entries");
  
  let query = {}
  let query2 = {}
  let sort = {}
  query[field] = { "$gte": 1000}
  query2[field] = 1
  sort[field] = -1
  
  return collection.find(query, query2)
      .skip(arg1)
      .limit(arg2-arg1)
      .sort(sort)
      .toArray()
      .then(items => {
        items.forEach(function(item, index, items) {
          var i = { }
          i["Name"] = item["_id"];
          i["Rank"] = items.indexOf(item) + 1;
          i["Experience"] = item[field]; 
          
          items[index] = i
        });
        return items;
      });
};

I’m a little skeptical about the forEach loop in the end and creating a new object every iteration, but I’m not sure how else to insert the players “Rank” into to the object and replace the original field with “Experience” (or whatever field is set on the client to deserialize into), since the client doesn’t know what the field being queried is.

There is probably a way to replace that foreach loop by an aggregation pipeline instead.
The field renaming is trivial with a $project or $addFields. The rank might be a little trickier I guess there is way.

Cheers,
Maxime.

1 Like