2013-03-16 60 views
3

假设之前,我有以下的文档结构:匹配的唯一的一组领域的最新文件聚集

> 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_idgroup_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管道来完成,但是这被证明是代价高昂处理大量文件时。这就是为什么我想尽早限制文件的原因。

编辑:

我还没有想出了一个很好的解决方案,如果该文档结构本身对我的使用情况没有优化,所以我想。我是否必须更新字段以支持我想实现的目标?非常感谢的建议。

编辑:

我其实寻找一个类似于预计How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL? MongoDB中实现不同的是它涉及到两个不同的字段值。此外,$match操作至关重要,因为它使结果集动态变化,过滤器的范围可以匹配标签或日期范围内。

编辑:

由于我的用例的复杂性我试图用一个简单的比喻,但这被证明是令人困惑的。以上是现在用例的简化形式。对不起,我创建了混乱。

+0

样本结构更新为与实际文档结构相匹配。 – MervS 2013-03-20 07:49:20

+0

对此问题的任何建议? – MervS 2013-03-22 01:51:58

回答

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

如果我wan't到组上USER_ID &地址和我wan't,我们都需要创建这样一个关键的最后日期的消息:

{ 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" }, 
} } 
); 

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

+0

一些评论:我想首先执行一个'$ project'来限制将要进入管道的字段。另外,我的'tags'字段是一个数组,所以我将不得不执行'$ unwind'的权利?纠正我,如果我错了,但在这些操作后,索引不再可用,不是吗? – MervS 2013-03-19 02:08:58

+0

是的,如果你不需要所有的领域,你应该$项目管道来节省内存。是的,你将需要一个$ unwind管道 - 没有测试过,如果你可以使用这个键 - 你应该试试看。 – notz 2013-03-19 23:11:58

+0

从http://docs.mongodb.org/manual/applications/aggregation/#pipeline-operators-and-indexes看来,我可能无法利用这些操作中的索引。我忘了提及在我的实际模式中,'标签'数组是在嵌入式文档中,我需要'$ project'将它移动到文档的第一层。 – MervS 2013-03-20 02:32:44

0

嗯,没有好的方法来做到这一点,你只需要挑出每个作者的最新作品,而不是你需要挑选出所有文档,排序,然后在作者上进行分组:

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到那个集合。这会更加优化。

+0

问题在于预聚合将很难实现,因为在我的用例分组是动态的情况下,用户最初可以用各种方式从'posts'中执行过滤,例如,在一系列日期内,匹配一组标签等等。因此,一个组的最新帖子可能是相对的(我希望你明白了)。 – MervS 2013-03-26 08:44:59

+0

@ nyde1319为什么你需要输出文件?难道你不能在作者的最新帖子中通过标签进行匹配吗?为什么每个作者和标签需要输出展开的文档? – Sammaye 2013-03-26 08:49:48

+0

在我的用例中,用户可以对帖子集合执行过滤器。现在,用户还可以选择根据一组标签对结果进行分组。在我的例子中,结果可以按照'tag_doc'或'other_tags'的不同值进行分组。在每个结果组中,只有最新的帖子应该被记录。我们可以说一些帖子可以是单个帖子的不同版本。也许后作者的例子可能不是我的实际案例很好的比喻。 – MervS 2013-03-26 08:56:16

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} 
) 
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"} 
       } 
    } 
); 

你要避免$最大的时候可以排序美元,并采取$第一,特别是如果你有UPLOADED_AT这将允许指数您可以避免任何内存分类,并显着降低流水线处理成本。显然,如果您有其他“数据”字段,您可以将它们与(或代替)“名称”和“tag_doc”一起添加。

+0

我假设'$ match'和'$ sort'管道是可以互换的,并且都会使用索引。 – MervS 2013-09-19 02:49:44

+0

是正确的 - 如果有支持它们的索引,它们将在处理期间合并。 – 2013-09-19 19:51:46

+0

还有一件事,当你使用'$ first'时,你能确保'latest','Name'和'tag_doc'的值属于同一个文件吗? – MervS 2013-09-20 00:48:23