在聚合之前匹配一组唯一字段的最新文档

时间:2013-03-16 01:05:09

标签: 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': ['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,只检索Logcase_id的每个唯一组合的所有最新group_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中寻找类似于How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL?中预期的实现,除了它涉及两个不同的字段值。此外,$match操作至关重要,因为它会使结果集动态化,过滤器范围匹配标记或在一系列日期范围内。

修改

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

4 个答案:

答案 0 :(得分:1)

我做了类似的事情。但是匹配不可能,但只有一个组管道。诀窍是使用多键和正确的排序:

   { 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" }

如果我不想分组user_id&地址,我想要创建一个像这样的密钥的最新日期的消息:

{ user_id:1, address:1, date_sent:-1 }

然后你就可以执行不带排序的聚合,这会更快,并且可以在具有副本的分片上运行。如果你没有正确排序顺序的密钥你可以添加一个排序管道,但是你不能将它与分片一起使用,因为所有转移到mongos和分组都是完成它们(也会得到内存限制问题)

 db.user_messages.aggregate(
 { $match: { user_id:1 } },
 { $group: {
     _id: "$address",
     count: { $sum : 1 },
     date_sent: { $max : "$date_sent" },
     message: { $first : "$message" },
 } }
);

没有记录它应该像这样工作 - 但确实如此。我们在生产系统上使用它。

答案 1 :(得分:1)

我会使用另一个集合来动态“创建”搜索结果 - 发布新帖子 - 每次发布新博客帖子时都会在此新集合中插入文档。

每个新的作者/标签组合都会作为新文档添加到此集合中,而具有现有组合的新帖子只会使用新博客帖子的内容(或对象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}
)

答案 2 :(得分:0)

嗯,没有好办法以这样的方式做到这一点,你只需要挑选出每位作者的最新成员,而你需要挑选出所有文件,排序,然后分组作者:< / p>
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集合中搜索所有文档,这些文档会对您所需的内容进行$in查询,然后更新/上传created_atpost_id那个集合。这将更加优化。

答案 3 :(得分:0)

你走了:

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并且取$ first时你想要避免$ max,特别是如果你有upload_at的索引,这将允许你避免任何内存排序并显着降低管道处理成本。显然,如果你有其他“数据”字段,你可以将它们与(或代替)“Name”和“tag_doc”一起添加。