Get random document from a Meteor collection
meteor, mongodb
Solution
Currently the MongoDB query language has no random-operator (although there is an open feature-request ticket for that).
Update Version 3.2: You can now use the `$sample` aggregation operator to obtain a random sample.
collection.aggregate(
[ { $sample: { size: 1 } } ]
)
If you can't or don't want to use it, there are some workarounds, but they aren't pretty.
One is to use `db.collection.count()` to get the number of documents in the collection. Then you can select the `n`-th document with `db.collection.find().skip(n).limit(1)`. But when the collection is large this can take a while, because the whole collection needs to be iterated with a cursor.
Another is to add a field `rand` with a random floating-point number between 0.0 and 1.0 to each document when you insert them. Then you can generate another random number `r` and do `db.collection.find({rand:{$gt:r}}).sort({rand:1}).limit(1)` to get the next greater one. When you have an index on the `rand` field, this will be very fast. But the randomness won't be uniformly distributed, because those documents which happen to have a larger gap between them and their predecessor will be picked more frequently. Also, when `r` happens to be larger than the largest in the collection, no result will be returned. In that case you should try again with the same number but this time with `rand:{$lte:r}` and `sort({rand:-1})`. When this doesn't return a document either, the collection is empty (or at least has no documents with a `rand` field).
The only corner-case where you can quickly and fairly select a random document is when your collection doesn't change (or at least doesn't change frequently). In that case you can number all the documents with consecutive integers starting with 0, index that field, and `find()` for a random number between 0 and your already known number of documents.
Problem
What would be the most efficient algorithm for fetching a random document from a Meteor collection, given that there is no numeric index? (There is another question which deals with doing so in MongoDB using the `skip` method, but this doesn't seem to be supported in Meteor). The inefficient way I came up with was selecting all the records and iterating up to a random number, but that is clearly getting expensive and cumbersome as the collection size grows.