Mongoose: How to get a random document

Updated: December 30, 2023 By: Guest Contributor Post a comment

Fetching a random document from a Mongoose collection is a common requirement and there are multiple ways to achieve it. This article discusses three methods, highlighting the pros and cons of each.

Aggregation Pipeline

This solution employs the aggregation framework to randomize documents. The steps are as follow:

  • Create an aggregation pipeline.
  • Add a $sample stage to the pipeline to select one random document.
  • Execute the aggregation.

Example:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const YourSchema = new Schema({ /* your schema definition */ });
const YourModel = mongoose.model('YourModelName', YourSchema);

async function getRandomDocument() {
  const randomDoc = await YourModel.aggregate([{ $sample: { size: 1 } }]);
  return randomDoc[0];
}

Pros: Simple and utilizes built-in MongoDB functionality.

Cons: Performance may degrade on large collections without an index. Not as random on sharded clusters.

Skip and Limit Method

This method involves skipping a random number of documents and retrieving one.

The steps:

  • Count the total number of documents.
  • Generate a random number to skip.
  • Use find() with skip() and limit() to retrieve the document.

Example:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const YourSchema = new Schema({ /* your schema definition */ });
const YourModel = mongoose.model('YourModelName', YourSchema);

async function getRandomDocument() {
  const count = await YourModel.countDocuments();
  const random = Math.floor(Math.random() * count);
  const randomDoc = await YourModel.findOne().skip(random).limit(1);
  return randomDoc;
}

Pros: Simple and effective for small to medium collections.

Cons: Inefficient for large collections; performance cost for skip() grows with the value.

Random Field Method

Add a field to store a random number and query based on it. Below’s the process to follow:

  • Add a random number field in your schema and fill it whenever a document is saved.
  • Generate a random number and find a document with a field value greater or equal to the random number.
  • Sort by the random field and limit the result to one document.

Code example:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const YourSchema = new Schema({
  /* your schema definition */,
  random: { type: Number, default: () => Math.random() }
});
YourSchema.pre('save', function (next) {
  this.random = Math.random();
  next();
});

const YourModel = mongoose.model('YourModelName', YourSchema);

async function getRandomDocument() {
  const random = Math.random();
  const randomDoc = await YourModel.findOne({ random: { $gte: random } }).sort({ random: 1 }).limit(1);
  if (randomDoc) return randomDoc;
  return YourModel.findOne().sort({ random: 1 }).limit(1);
}

Pros: Good performance even on large collections, truly random.

Cons: Requires schema modification and additional storage/maintenance for the random field.

Conclusion

When selecting a method for fetching a random document, consider your collection size and performance needs. The aggregation pipeline is best for simplicity and mostly random results. On smaller collections, skipping might be acceptable. For regular use on large collections, the random field method is likely the best approach as it offers consistent performance.