【问题标题】:How to paginate aggregation query results in MongoDB and get a total document count (Node.js + Mongoose)?如何在 MongoDB 中对聚合查询结果进行分页并获得总文档数(Node.js + Mongoose)?
【发布时间】:2021-01-23 05:41:49
【问题描述】:

我想通过 $limit 和 $skip 对查询结果进行分页,并获取通过查询的文档总数。

我现在对结果进行分页是这样的:

  const startIndex = (page - 1) * limit
  const endIndex = page * limit
  const resultObject = {}

  resultObject.totalCount = await model.countDocuments(query).exec()

  if (endIndex < results.totalCount)
    resultObject.next = {page: page + 1, limit}

  if (startIndex > 0)
    results.prev = {page: page - 1, limit}

  resultObject.results = await query.limit(limit).skip(startIndex).exec()

这会产生两个问题:

  • 我执行了两次查询(您甚至可以在 1 中执行此操作吗?)
  • 由于某种原因,当我的查询是一个聚合时,countDocuments 返回 0 即使我执行 query.exec() 我得到正确的结果 (> 0)

所以我的问题是如何在获取文档计数的同时,像现在只使用 1 个查询(并使其与聚合查询一起使用)对结果进行分页,以便我可以构建我的 resultObject

【问题讨论】:

    标签: javascript node.js mongodb mongoose


    【解决方案1】:
    • “我执行了两次查询(你甚至可以在 1 中执行此操作吗?)”不,你不能,实现这一点的唯一方法是获取整个集合,然后在代码中返回所需的文档,这显然是一个漂亮的不好的解决方案。

    • 现在这有点棘手,从技术上讲,countDocuments 是一个只执行管道的包装器:

    该方法使用 $sum 表达式包装 $group 聚合阶段以执行计数

    意义

    db.collection.countDocuments({});
    

    等于:

    db.collection.aggregate([
        {
            $group: {
                _id: null,
                count: {$sum: 1}
            }
        }
    ]);
    

    但是不同的驱动程序支持此命令的方式不同,例如 nodejs Mongo 驱动程序不允许使用指定的某些表达式 here。您还使用了mongoose,这可能会有额外的变化。

    如果不查看您的确切查询,很难说出它失败的原因,但是如果您找不到我建议的原因,只需执行包装的管道即可。

    【讨论】:

      【解决方案2】:

      我找到了一种方法来获取查询的结果 AND 计数都在数据库的同一“行程”中(仍然是 2 个查询,但在数据库中,所以它很多 更快)

      关键是使用$facet(mongodb 3.4+):

      const pipeline = [
        {YOUR_QUERY},
        {
          $facet: {
            paginatedResults: [{$skip: startIndex}, {$limit: limit}],
            totalCount: [{$count: 'count'}]
          }
        },
        {
          $addFields: {
            total: {
              $ifNull: [{ $arrayElemAt: ['$totalCount.count', 0]}, 0]
            }
          }
        },
        {
          $project: {
            paginatedResults: 1,
            total: 1
          }
        }
      ]
      

      说明$facet 在同一个输入(我们的查询结果)上执行多个管道,我们可以有 1 个管道用于对结果进行分页,1 个用于计算总数。

      由于$count 返回一个包含count 字段的对象,并且facet 包装在一个数组中,$addFields 管道将实际的count 值提取为total 字段。然后我只使用$project 删除不再需要的totalCount,这就是结果:

        {
           paginatedResults: [RESULTS],
           total: 34
        }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-08-02
        • 1970-01-01
        • 2020-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-05
        • 2020-03-05
        相关资源
        最近更新 更多