Nodejs中Mongodb如何随机的查询文档记录

Nodejs中Mongodb如何随机的查询文档记录

简述,摘要:在实际应用场景中,几乎都会有随机获取数据记录的需求。而这个需求在Mongodb却不是很好实现,就目前而言,大致上有三种解决方案:

  1. 先计算出一个从0到记录总数之间的随机数,然后采用skip(yourRandomNumber)方法。
  2. 为每一条记录增设random字段,插入数据时赋值为Math.random(),查询时采用$gte和$lte。
  3. 借助Mongodb对地理空间索引(geospatial indexes)的支持,从而可以在第二种方法的基础上来实现随机记录的获取。

因为Mongodb是不建议使用skip方法的,所以这里就略去第一种方法吧。 ##方法二##

> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala0...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala1...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala2...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala3...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala4...' })
/* more records... */

/* create index */ > db.twitter.ensureIndex({ username: 1, random: 1 })

> rand = Math.random() > result = db.twitter.findOne({ username: ‘heroic’, random: { $gte: rand } }) > if (result == null) { > result = db.twitter.findOne({ username: ‘heroic’, random: { $lte: rand } }) > }

##方法三## > db.twitter.save({ username: ‘heroic’, random: [Math.random(), 0], content: ‘balabala0…’ }) > db.twitter.save({ username: ‘heroic’, random: [Math.random(), 0], content: ‘balabala1…’ }) > db.twitter.save({ username: ‘heroic’, random: [Math.random(), 0], content: ‘balabala2…’ }) > db.twitter.save({ username: ‘heroic’, random: [Math.random(), 0], content: ‘balabala3…’ }) > db.twitter.save({ username: ‘heroic’, random: [Math.random(), 0], content: ‘balabala4…’ }) /* more records… */

/* create index */
> db.twitter.ensureIndex({ username: 1, random: '2d' })

> result = db.twitter.findOne({ username: 'heroic', random: { $near: [Math.random(), 0] } })

更多关于Mongodb地理空间索引资料,请参见这里

目前这几种方案似乎都不是很理想,但是也没有其他办法了,所以广大程序员们就相约到Mongodb的官方jira提了相应的需求,但是目前仍然没有任何的响应。可以参见这里,围观一下。 不知道大家谁还有更好的方法吗?


8 回复

Node.js 中 MongoDB 如何随机查询文档记录

在实际的应用场景中,我们经常会遇到需要随机获取数据记录的需求。而在 MongoDB 中实现这一需求并不是一件容易的事情。目前主要有以下三种解决方案:

  1. 使用 skip 方法:这种方法通过生成一个随机数并跳过该数量的记录来实现随机查询。但这种方式并不推荐,因为它会导致性能问题。

  2. 为每条记录增设 random 字段:在插入数据时为每条记录添加一个 random 字段,并在查询时使用 $gte$lte 操作符。

  3. 利用地理空间索引:在第二种方法的基础上,通过地理空间索引(geospatial indexes)来实现随机记录的获取。

由于 MongoDB 不推荐使用 skip 方法,因此这里将主要介绍第二种和第三种方法。

方法二:为每条记录增设 random 字段

首先,在插入数据时为每条记录添加一个 random 字段,并确保每个记录的 random 字段值不同。接着创建一个复合索引,以便在查询时能高效地找到符合条件的记录。

const MongoClient = require('mongodb').MongoClient;

MongoClient.connect('mongodb://localhost:27017/mydatabase', (err, client) => {
    if (err) throw err;
    
    const db = client.db();
    const collection = db.collection('twitter');

    // 插入数据
    collection.insertMany([
        { username: 'heroic', random: Math.random(), content: 'balabala0...' },
        { username: 'heroic', random: Math.random(), content: 'balabala1...' },
        { username: 'heroic', random: Math.random(), content: 'balabala2...' },
        { username: 'heroic', random: Math.random(), content: 'balabala3...' },
        { username: 'heroic', random: Math.random(), content: 'balabala4...' }
    ], (err, result) => {
        if (err) throw err;
        
        // 创建索引
        collection.createIndex({ username: 1, random: 1 }, (err, res) => {
            if (err) throw err;
            
            // 获取随机数
            let rand = Math.random();
            // 查询随机记录
            collection.findOne({ username: 'heroic', random: { $gte: rand } }, (err, doc) => {
                if (err) throw err;
                
                if (!doc) {
                    // 如果没有找到大于等于随机数的记录,则查找小于等于随机数的记录
                    collection.findOne({ username: 'heroic', random: { $lte: rand } }, (err, doc) => {
                        if (err) throw err;
                        console.log(doc);
                        client.close();
                    });
                } else {
                    console.log(doc);
                    client.close();
                }
            });
        });
    });
});

方法三:利用地理空间索引

这种方法与第二种方法类似,但在插入数据时将 random 字段设置为一个数组 [Math.random(), 0],并在查询时使用 $near 操作符。

const MongoClient = require('mongodb').MongoClient;

MongoClient.connect('mongodb://localhost:27017/mydatabase', (err, client) => {
    if (err) throw err;
    
    const db = client.db();
    const collection = db.collection('twitter');

    // 插入数据
    collection.insertMany([
        { username: 'heroic', random: [Math.random(), 0], content: 'balabala0...' },
        { username: 'heroic', random: [Math.random(), 0], content: 'balabala1...' },
        { username: 'heroic', random: [Math.random(), 0], content: 'balabala2...' },
        { username: 'heroic', random: [Math.random(), 0], content: 'balabala3...' },
        { username: 'heroic', random: [Math.random(), 0], content: 'balabala4...' }
    ], (err, result) => {
        if (err) throw err;
        
        // 创建索引
        collection.createIndex({ username: 1, random: '2d' }, (err, res) => {
            if (err) throw err;
            
            // 获取随机数
            let rand = Math.random();
            // 查询随机记录
            collection.findOne({ username: 'heroic', random: { $near: [rand, 0] } }, (err, doc) => {
                if (err) throw err;
                console.log(doc);
                client.close();
            });
        });
    });
});

这两种方法都可以实现随机查询,但需要注意的是,这两种方法都有一定的局限性,特别是在处理大量数据时可能会有性能问题。如果需要更高效的随机查询,可能需要考虑其他数据库或数据结构。


LZ,目前貌似就这些方法了。
另,利用地理空间索引方式其实会有一些问题的,比如当需要sort时这个方案就挂了。
Mongodb的特性注定了官方是不会提供随机获取记录的方法的。。。

用id作为随机种子,不知道能否取出一个随机数的区间数列然后limit 1

以后会用的着的,防范未然嘛,哈哈…

对了,用sort也可以解决,我之前还没想到: db.links.find().sort({random: 1}).limit(1)[0]

用sort肯定是不行,和第一种方法一样,数据量大了会有问题。现在mongodb有没有更好的查询随机若干条数据的方法?

在Node.js中实现MongoDB随机查询文档记录,可以使用上述提到的方法二或方法三。以下是基于方法二的示例代码,该方法通过在每条记录中添加一个随机字段来实现随机查询:

示例代码

const MongoClient = require('mongodb').MongoClient;
const url = "mongodb://localhost:27017/";
const dbName = "yourDatabaseName";

MongoClient.connect(url, function(err, client) {
  if (err) throw err;
  const db = client.db(dbName);
  
  // 添加随机字段
  const insertDocuments = () => {
    db.collection('twitter').insertMany([
      { username: 'heroic', random: Math.random(), content: 'balabala0...' },
      { username: 'heroic', random: Math.random(), content: 'balabala1...' },
      // 更多记录...
    ], (err, res) => {
      if (err) throw err;
      console.log("Documents inserted");
      
      // 创建索引
      db.collection('twitter').createIndex({ username: 1, random: 1 }, (err, res) => {
        if (err) throw err;
        console.log("Index created");

        // 查询随机文档
        getRandomDocument();
      });
    });
  };

  const getRandomDocument = () => {
    const rand = Math.random();
    db.collection('twitter').findOne({ username: 'heroic', random: { $gte: rand } }, (err, result) => {
      if (err) throw err;
      if (result) {
        console.log(result);
      } else {
        db.collection('twitter').findOne({ username: 'heroic', random: { $lte: rand } }, (err, result) => {
          if (err) throw err;
          console.log(result);
        });
      }
    });
  };

  insertDocuments();
});

解释

  1. 插入数据:我们首先向twitter集合中插入多条记录,并且每条记录包含一个随机数random
  2. 创建索引:为了提高查询效率,我们在usernamerandom字段上创建索引。
  3. 随机查询:查询时,首先生成一个随机数rand,然后尝试查找满足条件random >= rand的记录。如果找不到,则查找满足条件random <= rand的记录。

这种方法虽然简单易行,但可能会影响性能,特别是在数据量较大的情况下。因此,在实际应用中需要权衡使用场景和性能要求。

回到顶部