Mongodb 聚合前为唯一字段集匹配最新文档

Mongodb 聚合前为唯一字段集匹配最新文档,mongodb,aggregation-framework,Mongodb,Aggregation Framework,假设我有以下文档结构: > db.logs.find() { 'id': ObjectId("50ad8d451d41c8fc58000003") 'name': 'Sample Log 1', 'uploaded_at: ISODate("2013-03-14T01:00:00+01:00"), 'case_id: '50ad8d451d41c8fc58000099', 'tag_doc': { 'group_x: ['TAG-1','TAG-2'], 'group_y': ['

假设我有以下文档结构:

> db.logs.find()
{
'id': ObjectId("50ad8d451d41c8fc58000003")
'name': 'Sample Log 1',
'uploaded_at: ISODate("2013-03-14T01:00:00+01:00"),
'case_id: '50ad8d451d41c8fc58000099',
'tag_doc': {
  'group_x: ['TAG-1','TAG-2'],
  'group_y': ['XYZ']
}
},
{
'id': ObjectId("50ad8d451d41c8fc58000004")
'name': 'Sample Log 2',
'uploaded_at: ISODate("2013-03-15T01:00:00+01:00"),
'case_id: '50ad8d451d41c8fc58000099'
'tag_doc': {
  'group_x: ['TAG-1'],
  'group_y': ['XYZ']
}
}

> db.cases.findOne()
{
'id': ObjectId("50ad8d451d41c8fc58000099")
'name': 'Sample Case 1'
}
是否有一种方法可以在聚合框架中执行
$match
,对于
案例id
组x
的每个唯一组合,只检索所有最新的
日志
?我确信这可以通过多个
$group
管道完成,但我希望尽可能立即限制通过
$match
操作符通过管道的文档数量。我想到的是类似于
$max
操作符的东西,只是它在
$match
中使用

非常感谢您的帮助

编辑:

到目前为止,我可以得出以下结论:

db.logs.aggregate(
  {$match: {...}}, // some match filters here
  {$project: {tag:'$tag_doc.group_x', case:'$case_id', latest:{uploaded_at:1}}},
  {$unwind: '$tag'},
  {$group: {_id:{tag:'$tag', case:'$case'}, latest: {$max:'$latest'}}},
  {$group: {_id:'$_id.tag', total:{$sum:1}}}
)
正如我所提到的,我想要的可以通过多个
$group
管道来完成,但在处理大量文档时,这会导致成本高昂。这就是为什么,我想尽早限制这些文件

编辑:

我还没有想出一个好的解决方案,所以我在想,如果文档结构本身没有针对我的用例进行优化。我是否必须更新字段以支持我想要实现的目标?非常感谢您的建议

编辑:

实际上,我正在mongodb中寻找一个类似于中预期的实现,只是它涉及两个不同的字段值。另外,
$match
操作也很关键,因为它使生成的集合具有动态性,过滤器的范围包括匹配的标记或日期范围

编辑:


由于我的用例的复杂性,我尝试使用一个简单的类比,但这被证明是令人困惑的。以上是实际用例的简化形式。很抱歉我造成了混乱。

我也做了类似的事情。但这是不可能的比赛,但只有一组管道。诀窍是使用具有正确排序的多键:

   { user_id: 1, address: "xyz", date_sent: ISODate("2013-03-14T01:00:00+01:00"), message: "test" }, { user_id: 1, address: "xyz2", date_sent: ISODate("2013-03-14T01:00:00+01:00"), message: "test" }
如果我不想在用户id和地址上分组,也不想在邮件中显示最新日期,我们需要创建一个密钥,如下所示:

{ user_id:1, address:1, date_sent:-1 }
然后,您就可以执行聚合而无需排序,这会更快,并且可以处理具有副本的碎片。如果您没有具有正确排序顺序的密钥,则可以添加排序管道,但不能将其用于碎片,因为所有传输到mongos和分组的操作都已完成(也会出现内存限制问题)


没有文件证明它应该是这样工作的——但确实如此。我们在生产系统中使用它。

Hmmm,没有一种最佳的方法可以做到这一点,即您只需要挑选每个作者的最新版本,而需要挑选所有文档,进行排序,然后按作者分组:

db.posts.aggregate([
    {$sort: {created_at:-1}},
    {$group: {_id: '$author_id', tags: {$first: '$tag_doc.tags'}}},
    {$unwind: '$tags'},
    {$group: {_id: {author: '$_id', tag: '$tags'}}}
]);
正如你所说,这不是最佳的,然而,这是我所想到的一切

老实说,如果您需要经常执行此查询,那么最好预先聚合另一个已包含您所需信息的集合,其形式为:

{
    _id: {},
    author: {},
    tag: 'something',
    created_at: ISODate(),
    post_id: {}
}

每次创建一篇新文章时,您都会查找此unqiue集合中的所有文档,这些文档在查询中填写您需要的内容,然后更新/上传在创建的
发布id
。这将更为理想。

我会使用另一个集合来动态“创建”搜索结果——在发布新文章时——每次发布新博客文章时,都会在这个新集合中增加一个文档

作者/标记的每一个新组合都会作为新文档添加到此集合中,而具有现有组合的新帖子只会使用新博客帖子的内容(或对象ID引用)更新现有文档

例如:

db.searchResult.update(       
... {'author_id':'50ad8d451d41c8fc58000099', 'tag_doc.tags': ["TAG-1", "TAG-2" ]},
... { $set: { 'Referenceid':ObjectId("5152bc79e8bf3bc79a5a1dd8")}},  // or embed your blog post here
... {upsert:true}
)
给你:

db.logs.aggregate(
  {"$sort"     : { "uploaded_at" : -1 } },
  {"$match"    : { ... } }, 
  {"$unwind"   : "$tag_doc.group_x" },
  {"$group"    : { "_id" : { "case" :'$case_id', tag:'$tag_doc.group_x'}, 
                   "latest" : { "$first" : "$uploaded_at"},
                   "Name" : { "$first" : "$Name" },
                   "tag_doc" : { "$first" : "$tag_doc"}
                 }
  }
);

当您可以对$sort进行排序并首先获取$max时,您希望避免使用$max,特别是如果您在上传的\u上有一个索引,该索引允许您避免任何内存中的排序,并显著降低管道处理成本。显然,如果您有其他“数据”字段,您会将它们与“名称”和“tag_doc”一起添加(或代替它们)。

一些评论:我正在考虑首先执行
$project
,以限制将进入管道的字段。另外,我的
标记
字段是一个数组,因此我必须执行
$unwind
对吗?如果我错了,请纠正我,但在这些操作之后,索引不再可用,不是吗?是的,如果不需要所有字段,则应该使用$project pipline来节省内存。是的,您将需要一个$unwind管道-尚未测试您是否可以使用此密钥-您应该尝试一下。从中可以看出,我可能无法在这些操作中利用索引。我忘了提到,在我的实际模式中,“tags”数组位于嵌入式文档中,我需要
$project
将其移动到文档的第一级。我尝试了您的建议,但我发现,使用您提供的示例,它只会按
地址
用户\u消息进行分组,但每个
计数
仍然包括每个组中较旧的
消息
s。样本结构已更新以匹配实际文档结构。对此问题有何建议?问题是预聚合将很难实现,因为在我的用例中分组是动态的,用户最初可以通过多种方式过滤
帖子
,例如,在一个日期范围内,匹配一组标签等。因此,一个组的最新帖子可能是相对的(我希望你明白了)。@nyde1319为什么需要文档输出?你就不能根据作者最新帖子中的标签来匹配吗?为什么每个作者和标签都需要输出非预期文档?在我的用例中,用户可以对一组帖子执行过滤。现在,用户还可以选择根据一组标记对结果进行分组。在我的示例中,结果可以按
tag的不同值分组_
db.logs.aggregate(
  {"$sort"     : { "uploaded_at" : -1 } },
  {"$match"    : { ... } }, 
  {"$unwind"   : "$tag_doc.group_x" },
  {"$group"    : { "_id" : { "case" :'$case_id', tag:'$tag_doc.group_x'}, 
                   "latest" : { "$first" : "$uploaded_at"},
                   "Name" : { "$first" : "$Name" },
                   "tag_doc" : { "$first" : "$tag_doc"}
                 }
  }
);