MongoDB:如何在 100 个集合中找到 10 个随机文档?
Posted
技术标签:
【中文标题】MongoDB:如何在 100 个集合中找到 10 个随机文档?【英文标题】:MongoDB: how to find 10 random document in a collection of 100? 【发布时间】:2014-09-08 12:18:05 【问题描述】:MongoDB 是否能够在不进行多次查询的情况下资助大量随机文档?
例如我在加载集合中的所有文档后在 JS 端实现,这很浪费 - 因此只想检查是否可以通过一个 db 查询更好地完成?
我在JS这边走的路:
获取所有数据 制作一个 ID 数组 洗牌 ID 数组(随机顺序) 将数组拼接到所需的文档数 通过按我们在前两次操作后留下的 ID 选择文档来创建文档列表,从整个集合中逐一选择两个主要缺点是我正在加载所有数据 - 或者我进行了多个查询。
非常感谢任何建议
【问题讨论】:
真的只有 100 份的 10 份文件吗?如果是这样,那么如果当前的解决方案有效,为什么还要优化呢? 嗯,这只是一个例子,我希望集合增长到1000s 【参考方案1】:很久以前就回答了这个问题,从那时起,MongoDB 有了很大的发展。
正如在另一个答案中发布的那样,MongoDB 现在支持 sampling within the Aggregation Framework,因为版本 3.2:
你可以这样做:
db.products.aggregate([$sample: size: 5]); // You want to get 5 docs
或者:
db.products.aggregate([
$match: category:"Electronic Devices", // filter the results
$sample: size: 5 // You want to get 5 docs
]);
不过,关于 $sample 操作符有some warnings:
(截至 2017 年 11 月 6 日,最新版本为 3.4)=> 如果不满足任何条件:
$sample 是流水线的第一阶段 N 小于集合中文档总数的 5% 集合包含 100 多个文档如果上述任何一个条件不满足,$sample 将执行 集合扫描,然后随机排序以选择 N 个文档。
就像上一个例子中的 $match
老答案
你总是可以跑:
db.products.find(category:"Electronic Devices").skip(Math.random()*YOUR_COLLECTION_SIZE)
但顺序不会是随机的,您将需要两次查询(一次计数以获取 YOUR_COLLECTION_SIZE)或估计它有多大(大约 100 条记录,大约 1000 条,大约 10000 条......)
您还可以使用随机数向所有文档添加一个字段并按该数字进行查询。这里的缺点是每次运行相同的查询时都会得到相同的结果。要解决此问题,您始终可以使用限制和跳过甚至排序。您也可以在每次获取记录时更新这些随机数(意味着更多查询)。
--我不知道你是在使用Mongoose、Mondoid还是直接使用Mongo Driver来实现任何特定语言,所以我会写关于mongo shell的所有内容。
因此,假设您的产品记录如下所示:
_id: ObjectId("..."),
name: "Awesome Product",
category: "Electronic Devices",
我建议使用:
_id: ObjectId("..."),
name: "Awesome Product",
category: "Electronic Devices",
_random_sample: Math.random()
那么你可以这样做:
db.products.find(category:"Electronic Devices",_random_sample:$gte:Math.random())
然后,您可以定期运行,以便定期更新文档的 _random_sample 字段:
var your_query = //it would impact in your performance if there are a lot of records
your_query = category: "Electronic Devices" //Update
//upsert = false, multi = true
db.products.update(your_query,$set:_random_sample::Math.random(),false,true)
或者只是每当您检索一些记录时,您可以更新所有记录或仅更新一些记录(取决于您检索到的记录数)
for(var i = 0; i < records.length; i++)
var query = _id: records[i]._id;
//upsert = false, multi = false
db.products.update(query,$set:_random_sample::Math.random(),false,false);
编辑
请注意
db.products.update(your_query,$set:_random_sample::Math.random(),false,true)
不会很好地工作,因为它会使用相同的随机数更新与您的查询匹配的所有产品。最后一种方法效果更好(在检索某些文档时更新它们)
【讨论】:
【参考方案2】:从 3.2 开始,有一种更简单的方法可以从集合中获取随机文档样本:
$样本 3.2 版中的新功能。
从其输入中随机选择指定数量的文档。
$sample 阶段的语法如下:
$sample: size: <positive integer>
Source: MongoDB Docs
在这种情况下:
db.products.aggregate([$sample: size: 10]);
【讨论】:
请注意,使用此方法可能会在响应中返回重复的文档。小心!【参考方案3】:这是我最后想到的:
var numberOfItems = 10;
// GET LIST OF ALL ID's
SchemaNameHere.find(, '_id': 1 , function(err, data)
if (err) res.send(err);
// shuffle array, as per here https://github.com/coolaj86/knuth-shuffle
var arr = shuffle(data.slice(0));
// get only the first numberOfItems of the shuffled array
arr.splice(numberOfItems, arr.length - numberOfItems);
// new array to store all items
var return_arr = [];
// use async each, as per here http://justinklemm.com/node-js-async-tutorial/
async.each(arr, function(item, callback)
// get items 1 by 1 and add to the return_arr
SchemaNameHere.findById(item._id, function(err, data)
if (err) res.send(err);
return_arr.push(data);
// go to the next one item, or to the next function if done
callback();
);
, function(err)
// run this when looped through all items in arr
res.json(return_arr);
);
);
【讨论】:
【参考方案4】:skip 不适合我。这是我的结论:
var randomDoc = db.getCollection("collectionName").aggregate([
$match :
// criteria to filter matches
,
$sample :
size : 1
]).result[0];
获取单个随机结果,匹配条件。
【讨论】:
【参考方案5】:示例可能不是最好的,因为您不会像那样获得虚拟。 相反,请在后端创建一个对结果进行洗牌的函数。 然后返回打乱后的数组而不是mongodb结果
【讨论】:
以上是关于MongoDB:如何在 100 个集合中找到 10 个随机文档?的主要内容,如果未能解决你的问题,请参考以下文章