2

我最近一直在玩弄 MongoDB 的聚合框架,并认为这将是解决我一直试图解决的问题的好方法。

因此,假设我正在编写讨论板软件,并且我有以下帖子文档结构:

{
  '_id': ObjectId,
  'created_at': datetime,
  'poster_id': ObjectId,
  'discussion_id': ObjectId,
  'body': string
}

我在posts集合中存储了以下(简化的)示例文档:

{
  '_id': 1,
  'created_at': '2013-08-18 12:00:00',
  'poster_id':  1,
  'discussion_id':  1,
  'body': 'imma potato'
}

{
  '_id': 2,
  'created_at': '2013-08-18 13:00:00',
  'poster_id':  1,
  'discussion_id':  1,
  'body': 'im still a potato'
}

{
  '_id': 3,
  'created_at': '2013-08-18 14:00:00',
  'poster_id':  2,
  'discussion_id':  1,
  'body': 'you are definitely a potato'
}

{
  '_id': 4,
  'created_at': '2013-08-18 15:00:00',
  'poster_id':  3,
  'discussion_id':  1,
  'body': 'Wait... he is potato?'
}

{
  '_id': 5,
  'created_at': '2013-08-18 16:00:00',
  'poster_id':  2,
  'discussion_id':  1,
  'body': 'Yes! He is potato.'
}

{
  '_id': 6,
  'created_at': '2013-08-18 16:01:00',
  'poster_id':  3,
  'discussion_id':  1,
  'body': 'IF HE IS POTATO... THEN WHO WAS PHONE!?'
}

我想要做的是返回一个独特的poster_ids 地图,他们的最新帖子_id按最新帖子降序排序。所以,最后,给定上面的示例代码,映射看起来非常类似于:

{
  3:6,
  2:5,
  1:2
}

这是我使用 pymongo 实现的 MongoDB 聚合框架在 Python 中编写的一个方法示例:

def get_posters_with_latest_post_by_discussion_ids(self, discussion_ids, start=None, end=None, skip=None, limit=None, order=-1):
    '''Returns a mapping of poster ids to their latest post associated with
    the given list of discussion_ids. A date range, ordering and paging properties
    can be applied.
    '''
    pipelines = []

    if order:
        pipelines.append({ '$sort': { 'created_at': order } })

    if skip:
        pipelines.append({ '$skip': skip })

    if limit:
        pipelines.append({ '$limit': limit })

    match = {
        'discussion_id': {
            '$in': discussion_ids
        }
    }

    if start and end:
        match['created_at'] = {
            '$gte': start,
            '$lt': end
        }

    pipelines.append({ '$match': match })
    pipelines.append({ '$project': { 'poster_id': '$poster_id' } })
    pipelines.append({ '$group': { '_id': '$poster_id', 'post_id': { '$first': '$_id' } } })

    results = self.db.posts.aggregate(pipelines)

    poster_to_post_map = {}
    for result in results['result']:
        poster_to_post_map[result['_id']] = result['post_id']

    return poster_to_post_map

现在我有了映射,我可以分别查询postersposts集合以获取完整的文档,然后将它们组合在一起进行显示。

现在,问题不在于它不起作用,它确实……有点。假设我的帖子数量要多得多,我想翻阅带有最新帖子的海报列表。如果我的页面限制是“每页 10 个海报”并且在生成的 10 个文档中存在一个包含 2 个或更多帖子的海报,那么我实际上在我的地图中返回的项目少于 10 个。

例如,我有 10 个帖子,1 个海报在初始结果中有 3 个帖子。然后,聚合框架将丢弃其他 2 个帖子并将最新的帖子与该用户相关联,从而生成包含 8 个条目的地图,而不是 10 个。

这非常令人沮丧,因为我无法可靠地对结果进行分页。我也无法准确确定我是否在结果的最后一页,因为一组结果可能会或可能不会返回 0 个或更多匹配项。

什么,如果有的话,我在这里做错了吗?

我要完成的工作很简单,聚合框架似乎非常适合我的问题。

如果它是传统关系数据库上的存储过程,这将非常简单,但是当我们转向无模式文档存储时,这就是我们所牺牲的;关系在数据库的上下文之外进行管理。

无论如何,代码应该很容易理解,我会回答你可能有的任何问题。

无论哪种方式,感谢您花时间阅读。:)

编辑:已解决

以下是面向未来观众的解决方案要点:https ://gist.github.com/wilhelm-murdoch/6260469

4

1 回答 1

2

如果您考虑如何描述聚合框架,它实际上是一个非常容易解决的问题。

取自文档

从概念上讲,来自集合的文档通过一个聚合管道,该管道在这些对象通过时对其进行转换。对于那些熟悉类 UNIX shell(例如 bash)的人来说,这个概念类似于用于将文本过滤器串在一起的管道(即 |)。

您之前可能已经阅读过,但再次解释的原因是您可以以几乎任何顺序将操作传递到该管道 - 并且不止一次。例如在 MYSQL 中,LIMIT它总是列在查询的末尾,并应用于所有其他分组函数之后的结果集。

在 MongoDB 中,操作按照您将它们添加到管道的顺序运行。所以操作顺序很重要。

查看您上面的代码,您似乎实际上正在获取所有内容 - 并且取决于您的 IF 语句,首先对其进行排序,应用您的偏移量和限制,然后在投影和分组之前与该结果集进行匹配。

所以 - 长话短说 - 看起来你需要重新排序:

pipelines = []

match = {
    'discussion_id': {
        '$in': discussion_ids
    }
}

if start and end:
    match['created_at'] = {
        '$gte': start,
        '$lt': end
    }

pipelines.append({ '$match': match })

if order:
    pipelines.append({ '$sort': { 'created_at': order } })

pipelines.append({ '$project': { 'poster_id': '$poster_id' } })
pipelines.append({ '$group': { '_id': '$poster_id', 'post_id': { '$first': '$_id' } } })

if skip:
    pipelines.append({ '$skip': skip })

if limit:
    pipelines.append({ '$limit': limit })

results = self.db.posts.aggregate(pipelines)
于 2013-08-18T07:27:07.220 回答