如何使用 Mongoose 进行查询,获得 N 个结果,但结合它找到的任何满足特定条件的文档?
Posted
技术标签:
【中文标题】如何使用 Mongoose 进行查询,获得 N 个结果,但结合它找到的任何满足特定条件的文档?【英文标题】:How to make a query using Mongoose that gets N results, but combines any documents it finds that meet certain criteria? 【发布时间】:2015-03-08 12:16:59 【问题描述】:我在 Mongoose 中有一个 Comments 集合,以及一个返回最近五个(任意数量)Comments 的查询。
每个评论都与另一个文档相关联。我想做的是进行一个查询,返回最近的 5 个 cmets,其中 cmets 与相同的其他文档相关联。
所以不是这样的列表:
results = [
_id: 123, associated: 12 ,
_id: 122, associated: 8 ,
_id: 121, associated: 12 ,
_id: 120, associated: 12 ,
_id: 119, associated: 17
]
我想返回一个这样的列表:
results = [
_id: 124, associated: 3 ,
_id: 125, associated: 19 ,
[
_id: 123, associated: 12 ,
_id: 121, associated: 12 ,
_id: 120, associated: 12 ,
],
_id: 122, associated: 8 ,
_id: 119, associated: 17
]
请不要太担心数据格式:这只是一个草图,试图展示我想要的那种东西。我想要一个指定大小的结果集,但有些结果是根据某些标准分组的。
显然,执行此操作的一种方法是只进行查询、抓取并修改结果,然后再次递归地进行查询,直到结果集达到所需的长度。那样子显得很别扭。有没有更好的方法来解决这个问题?我无法在 Google 搜索中以一种让我接近任何可能有洞察力的人的方式来表达它。
【问题讨论】:
我在这里看到的问题是这样的。对于您在此处按“12”进行的分组,所画的线在哪里,上面写着“在此组中停止列出结果”。在我看来,从上到下至少有一个条目在“最近的 5 个”之内,但是你正在分组的那些呢?需要有一个约束,例如没有帖子可以比最旧的帖子更早。否则,这里的危险是在任何分组中获取大量帖子。那么什么是规则呢?也许是这个建议或其他什么? 我宁愿对分组项目没有限制:将它们想象成一个折叠的下拉列表,其中可以包含任意数量的项目而不会导致 UI 问题。 尝试在这里接受教育。想想看。如果您按“关联用户”排序,然后按日期降序排序,那么在移动到下一个用户之前您在哪里停止。如果您按日期排序,然后按“关联用户”排序,那么您又会在哪里停下来?在收集了多少个可能的分组之后。某处必须有一条线。人们制造了很多 cmets。 好的:我不反对某种限制。鉴于此,我该如何处理? 【参考方案1】:这是一个聚合管道查询,可以满足您的要求:
db.comments.aggregate([
$group: _id: "$associated", maxID: $max: "$_id", cohorts: $push: "$$ROOT",
$sort: "maxID": -1 ,
$limit: 5
])
样本数据中缺少任何其他字段作为排序依据,我使用了 $_id。
如果您想要结构更接近您提供的示例结果集的结果,您可以在末尾添加 $project
:
db.comments.aggregate([
$group: _id: "$associated", maxID: $max: "$_id", cohorts: $push: "$$ROOT",
$sort: "maxID": -1 ,
$limit: 5 ,
$project: _id: 0, cohorts: 1
])
这将只打印结果集。请注意,即使不共享关联对象的 cmets 也将位于数组中。它将是一个长度为 1 的数组。
如果您担心按照 Neil Lunn 的建议限制分组中的结果,那么开头的 $match
可能是一个聪明的主意。
db.comments.aggregate([
$match: createDate: $gte: new Date(new Date() - 5 * 60000) ,
$group: _id: "$associated", maxID: $max: "$_id", cohorts: $push: "$$ROOT",
$sort: "maxID": -1 ,
$limit: 5 ,
$project: _id: 0, cohorts: 1
])
假设您有一个createDate
类型字段,这将仅包括在最后 5 分钟内创建的 cmets。如果这样做,您也可以考虑使用它作为排序依据而不是“_id”。如果您没有 createDate
类型字段,我不确定如何最好地限制分组的 cmets,因为我不知道“当前 _id”以存在“当前时间”的方式。
【讨论】:
这是我需要的。当我昨晚扫描文档时,我的 Mongo[oose] fu 太弱了,无法识别聚合可以为我提供所需的东西。感谢您的帮助!【参考方案2】:老实说,我认为您在这里问了很多问题,但我自己并不能真正看到该实用程序,但如果我错过了一些有用的东西,我总是很高兴向我解释。
底线是您希望按日期从最后五个不同用户中获取 cmets,然后按这些用户对其他 cmets 进行某种分组。最后一部分是无论你想如何攻击,我都认为规则存在困难,但我会尽量保持最简短的形式。
在任何类型的单个查询中都不会发生这种情况。但是可以做一些事情来使其成为有效的服务器响应:
var DataStore = require('nedb'),
store = new DataStore();
async.waterfall(
function(callback)
Comment.aggregate(
[
"$match": "postId": thisPostId ,
"$sort": "associated": 1, "createdDate": -1 ,
"$group":
"_id": "$associated",
"date": "$first": "$createdDate"
,
"$sort": "date": -1 ,
"$limit": 5
],
callback);
,
function(docs,callback)
async.each(docs,function(doc,callback)
Comment.aggregate(
[
"$match": "postId": thisPostId, "associated": doc._id ,
"$sort": "createdDate": -1 ,
"$limit": 5 ,
"$group":
"_id": "$associated",
"docs":
"$push":
"_id": "$_id", "createdDate": "$createdDate"
,
"firstDate": "$first": "$createdDate"
],
function(err,results)
if (err) callback(err);
async.each(results,function(result,callback)
store.insert( result, function(err, result)
callback(err);
);
,function(err)
callback(err);
);
);
,
callback);
,
function(err)
if (err) throw err;
store.find().sort( "firstDate": - 1 ).exec(function(err,docs)
if (err) throw err;
console.log( JSON.stringify( docs, undefined, 4 ) );
);
);
现在我在文档和数组中添加了更多文档属性,但是基于您的示例的简化形式随后会如下所示:
results = [
"_id": 3, "docs": [124] ,
"_id": 19, "docs": [125] ,
"_id": 12, "docs": [123,121,120] ,
"_id": 8, "docs": [122] ,
"_id": 17, "docs": [119]
]
因此,基本的想法是首先找到您的不同“用户”,他们通过基本上砍掉最后 5 个来发表评论。这里不过滤某种范围,会遍历整个集合以获得这些结果,所以最好以某种方式限制这一点,例如在最后一个小时或最后几个小时内或根据需要进行合理的操作。只需将这些条件与与 cmets 关联的当前帖子一起添加到 $match
。
一旦您拥有了这 5 个,那么您希望获得这些用户为多个 cmet 提供的任何可能的“分组”详细信息。同样,通常建议对时间范围进行某种限制,但作为一般情况,这只是在当前帖子中查找用户最近的 cmets,并将其限制为 5。
这里的执行是并行完成的,这将使用更多资源,但考虑到无论如何只有 5 个查询要运行,它相当有效。与您的示例输出相比,此处的数组位于文档结果中,它包含每个评论的原始文档 id 值以供参考。与文档相关的任何其他内容都将被推送到数组中以及需要的(即评论的内容)。
这里的另一个小技巧是使用nedb 作为将每个查询的输出存储在“内存中”集合中的一种方式。这实际上只需要一个标准的哈希数据结构,但nedb 为您提供了一种方法,同时保持您可能习惯的 MongoDB 语句形式。
获得所有结果后,您只需将它们作为输出返回,并按所示排序以保留最后评论者的顺序。实际的 cmets 在数组中为每个项目分组,您可以遍历它以输出您喜欢的方式。
这里的底线是,您要求的是“前 N 个结果问题”的复合版本,这是 MongoDB 经常提出的问题。我之前已经写过解决这个问题的方法,以展示如何在单个聚合管道阶段实现它,但对于相对较小的结果集而言,它确实不实用。
如果您真的想加入疯狂,那么您可以查看Mongodb aggregation $group, restrict length of array 以获得更详细的示例之一。但是为了我的钱,我会在任何一天运行并行查询。 Node.js 有合适的环境来支持它们,否则你会疯掉的。
【讨论】:
我会退后一步:我想在我的应用程序中有一个小部件来显示最近的 cmets。但我不希望该列表被单个用户在单个游戏中创建的最后 30 个 cmets 所覆盖。我希望它更能代表不同用户最近的一般活动。无论如何,我非常感谢您抽出时间尝试更好地解释情况并给我一些背景信息。谢谢! @Nate 所以在我看来,你仍然必须采用这种方法才能真正做到这一点。无论是将单个用户制作的多个 cmets 推送到一个数组中,还是只保留他们的第一条或最后一条评论。但是要获得“外部” 30,您首先需要一个不同的列表,然后查询这些不同用户的详细信息。否则只是猜测。你也许可以接受猜测,但这不是你问的问题。以上是关于如何使用 Mongoose 进行查询,获得 N 个结果,但结合它找到的任何满足特定条件的文档?的主要内容,如果未能解决你的问题,请参考以下文章
设置 Mongoose 模式进行身份验证后,如何使用 Mongodb 查询数据库?