35

I know how to sort queries in MongoDB by multiple fields, e.g., db.coll.find().sort({a:1,b:-1}).

Can I sort with a user-defined function; e.g., supposing a and b are integers, by the difference between a and b (a-b)?

Thanks!

gilesc
  • 1,969
  • 1
  • 14
  • 16
  • 1
    I'm running into the same issue. Were you able to find a way to accomplish this on the server side? I'd really rather not have to do this on the client, or add the additional field. – Rex Morgan Mar 09 '11 at 19:17

3 Answers3

31

UPDATE: This answer appears to be out of date; it seems that custom sorting can be more or less achieved by using the $project function of the aggregation pipeline to transform the input documents prior to sorting. See also @Ari's answer.


I don't think this is possible directly; the sort documentation certainly doesn't mention any way to provide a custom compare function.

You're probably best off doing the sort in the client, but if you're really determined to do it on the server you might be able to use db.eval() to arrange to run the sort on the server (if your client supports it).

Server-side sort:

db.eval(function() { 
  return db.scratch.find().toArray().sort(function(doc1, doc2) { 
    return doc1.a - doc2.a 
  }) 
});

Versus the equivalent client-side sort:

db.scratch.find().toArray().sort(function(doc1, doc2) { 
  return doc1.a - doc2.b 
});

Note that it's also possible to sort via an aggregation pipeline and by the $orderby operator (i.e. in addition to .sort()) however neither of these ways lets you provide a custom sort function either.

Naman
  • 27,789
  • 26
  • 218
  • 353
mjs
  • 63,493
  • 27
  • 91
  • 122
  • 2
    Yup. After further digging I found that there's been an RFE filed to allow for convenience functions to do exactly this. I definitely don't like the looks of that `toArray()` for my ~10m documents, but that's apparently the state of affairs. – gilesc Jul 09 '10 at 14:56
  • Hi @gilesc .Would you please tell me how did you exactly use 'RFE filed' in detail? I'm in the same situation. – dotslashlu May 04 '12 at 03:38
  • 1
    @Wasabi I think he was referring to this: https://jira.mongodb.org/browse/SERVER-153 It's not possible, but there's a ticket open where they are considering implementing it. But it's been open for years, so not sure if it will ever happen. – rmarscher May 09 '12 at 17:03
  • Your sorting function may be wrong. It should be ``return (doc1.a - doc1.b) - (doc2.a - doc2.b)`` – Hank May 11 '14 at 07:08
  • 1
    As demonstrated in @Ari's answer, you can use $project to implement your function and assign it to a field, and then sort on that. – Bob Kerns Nov 06 '15 at 05:30
  • there is a helpful article at http://koenaerts.ca/mongodb-aggregation-custom-sort/ – Dayong Jan 09 '18 at 21:14
19

Ran into this and this is what I came up with:

db.collection.aggregate([
  { 
    $project: {
      difference: { $subtract: ["$a", "$b"] }
      // Add other keys in here as necessary
    }
  },  
  { 
    $sort: { difference: -1 } 
  }
])
Ari
  • 990
  • 12
  • 12
11

Why don't create the field with this operation and sort on it ?

shingara
  • 46,608
  • 11
  • 99
  • 105
  • 3
    I still need access to a and b individually. So I'd have to create a third field. That would work for my application, but as a general principle would be really bad policy. Supposing I had several integer fields, there would be a combinatorial explosion of derived attributes (a-b), (a-c), (a-d)... Even with just one field like this, it's really wasteful of space especially for a large collection. I'm upvoting and if no one else answers accepting, but there has to be a better way. In SQL this is as easy as `SELECT * FROM coll ORDER BY (a-b)` . – gilesc Jul 09 '10 at 13:39
  • 1
    Note that although you can do this in SQL, it is not very efficient. The database has to fetch all a and b values, calculate a-b, sort the results and return the associated records. With Mongo this would mean loading all documents into memory if you do not have an index on a and b. If you create a new field, you can create an index on this field, which will make this query really fast. – konrad Jan 06 '13 at 16:03
  • Great idea. As long as the value of a record's position is localized to data in the record itself, this perfectly amortizes the cost of computing the sort on insertion/update. – DoctorPangloss Jun 01 '13 at 23:14