MongoDB 聚合限制查找

15

我正在使用PyMongo中的$lookup成功地“连接”了两个集合(这个有效)。 我遇到的问题是,我要加入的第二个集合在返回所有记录时可能会超过BSON文档大小。

我希望使用$limit来限制允许连接到“match_docs”下的记录数量,例如:每个obj_id最多100条来自“comments”的记录:

db.indicators.aggregate([
  {
    "$lookup": {
      "from": "comments",
      "localField": "_id",
      "foreignField": "obj_id",
      "as": "match_docs"
    }
  }
])

我尝试了各种类型的$limit,似乎只限制了所有结果的总数,而不仅仅是联接的结果。


你不能在$lookup中设置限制,但是你并不需要这个来使查询工作。在你的聚合选项中添加{allowDiskUse: true}应该可以解决问题。 - felix
@felix OP讨论的是BSON文档大小限制,而不是内存限制。 - Neil Lunn
2
你能否在 $lookup 返回的内容上使用 $match 以减少结果吗?当管道阶段为 $lookup -> $unwind -> $match$match 中的条件(连续)和 $match 引用了来自 $lookup 的数组时,存在一种特殊情况,后两个阶段被“提升”到 $lookup 中。这是一种优化方式,可减少可能返回的条目数。 - Neil Lunn
@NeilLunn 你知道这个在查询形式下会是什么样子吗? - gleb1783
1
是的,你需要在 $lookup 之后立即使用 $unwind,最好在 $unwind 之后立即使用 $match。如果这比我已经说的更清楚,请这样做。也许你应该展示你整个“预期”的聚合管道,然后我们可以建议是否适合你的目的。 - Neil Lunn
4个回答

35

从MongoDB 3.6开始,您可以使用非相关子查询来限制查找:

db.indicators.aggregate([
{ $lookup: {
  from: 'comments',
  as: 'match_docs',
  let: { indicator_id: '$_id' },
  pipeline: [
    { $match: {
      $expr: { $eq: [ '$obj_id', '$$indicator_id' ] }
    } },
    // { $sort: { createdAt: 1 } }, // add sort if needed (for example, if you want first 100 comments by creation date)
    { $limit: 100 }
  ]
} }
])

1
$match后的$limit,正是我所需要的。 - Ofir G
3
这只有我不能用吗?不知何故,它限制了我查找的父级集合的结果,而不是“评论”集合的结果。 - Kisinga
我遇到了同样的问题,但是 $lookup 本身已经足够匹配了。只需要在查找中使用 $sort$limit 即可。非常好用。 - evolross

3
如果您在 $lookup 后立即执行 $unwind,则管道将被优化,基本上合并了这两个阶段,并帮助绕过可能由 $lookup 返回大量文档而导致的 16MB 限制。
请注意,如果外部集合中的单个文档加上本地集合中的文档大小超过 16MB,则此优化无法生效。

请注意,如果$unwind后面跟着的是针对外部集合的$match,则查询条件也会随着$unwind一起“提升”到$lookup中。您可以使用{ "explain": true }作为聚合参数来查看这一点。 - Neil Lunn
@NeilLunn,这里有一些注意事项:https://jira.mongodb.org/browse/SERVER-21612。此外,为了在后续的`$match`中使用索引,您需要拥有一个以`$lookup`中使用的字段开头,后跟`$match`中的字段的复合索引。 - Pete Garafano
索引与我在这里所说的无关,这是一个单独的问题。你在这里做出的两个回答看起来像是通过在谷歌上搜索术语得出的结果,可以通过示例澄清整个故事。 - Neil Lunn
@NeilLunn,我不确定我是否看到这是一个单独的问题。您说使用$lookup$unwind后使用$match将被优化。我只是补充说,为了使$match使用索引,索引必须采用特定形式。否则,即使由查询引擎进行优化,也可能会扫描大量文档以满足从结果$lookup结果集中满足$match。我提供了SERVER票证链接,以说明此管道优化中存在的注意事项。 - Pete Garafano
请提供示例代码。 - chovy

2
我已经弄清楚了。 $lookup->$match->$project
db.indicators.aggregate([{
    "$lookup": {
        "from": "comments"
        , "localField": "_id"
        , "foreignField": "obj_id"
        , "as": "match_docs"
    }
}, {
    "$match": {
        "match_docs": {
            "$exists": True
        }
    }
}, {
    "$project": {
        "match_docs_agg": {
            "$slice": ["$match_docs", 3]
        }
    }
}])

4
很抱歉打破你的幻想,但这并不能真正避免BSON限制被打破。测试数组字段的存在是"必要的",因为它总是被创建在$lookup的输出中。$slice只会在结果被拉入文档后才能减少其大小,因此BSON限制仍然可能被打破。这之所以对你起作用,仅因为拉入的文档实际上没有超出此限制。16MB已经相当大了。 - Neil Lunn
你是百分之百正确的,它在某种意义上做到了我想要的功能,即只返回连接列表中的“3”,但它仍然在管道过程中出现错误。 - gleb1783
你能否请您在问题中展示您打算执行的聚合操作?我所说的“意图”是$lookup操作以及接下来想要执行的聚合操作,即使只是$slice。然后我才能向您展示该如何实现。 - Neil Lunn
那么我们该如何修复呢? - chovy

-3

通过使用限制聚合,我们可以按要求筛选记录。根据我们的需求,我们可以传递限制值。这里有一个有用的链接 文档链接

db.getCollection('botAnalytics').aggregate([{
                    $lookup: {
                        from: "movie",
                        localField: "botKey",
                        foreignField: "key",
                        as: "botDetails",
                    },
                },
                {
                    $match: { applicationId: "15077a8c38657a61b844e6a" },
                       },
                 { $limit : 5 }])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接