web-dev-qa-db-ja.com

制限を使用するときにMongoDBでドキュメントの総数を取得する

MongoDBで作業している「ページネーション」ソリューションの最適化に興味があります。私の問題は単純明快です。通常、limit()機能を使用して返されるドキュメントの数を制限します。これにより、limit()関数を使用せずに冗長クエリを発行し、クエリ内のドキュメントの合計数も取得できるようにします。追加のリクエストを発行して、残りのドキュメントを取得します。

これを1つのクエリに凝縮する方法はありますか?ドキュメントの合計数を取得しますが、同時にlimit()?を使用してサブセットのみを取得しますこの問題について、私がアプローチしているのとは異なる考え方がありますか?

29
randombits

いいえ、他の方法はありません。 2つのクエリ-カウント用-制限付きまたは、別のデータベースを使用する必要があります。たとえば、Apache Solrは希望どおりに機能します。クエリはすべて制限されていますandはtotalCountを返します。

19
heinob

Mongodb3.4が導入しました $facet 集約

入力ドキュメントの同じセットの単一ステージ内で複数の集約パイプラインを処理します。

$facet および $group$limit 合計数を取得できます。

Mongodbでは以下の集約を使用できます3.4

db.collection.aggregate([
  { "$facet": {
    "totalData": [
      { "$match": { }},
      { "$skip": 10 },
      { "$limit": 10 }
    ],
    "totalCount": [
      { "$group": {
        "_id": null,
        "count": { "$sum": 1 }
      }}
    ]
  }}
])

$count mongodbで導入された集約3.6

Mongodbで以下の集計を使用できます3.6

db.collection.aggregate([
  { "$facet": {
    "totalData": [
      { "$match": { }},
      { "$skip": 10 },
      { "$limit": 10 }
    ],
    "totalCount": [
      { "$count": "count" }
    ]
  }}
])
16
Ashh

mongodb 3.4には方法があります:$ facet

できるよ

db.collection.aggregate([
  {
    $facet: {
      data: [{ $match: {} }],
      total: { $count: 'total' }
    }
  }
])

その後、2つの集約を同時に実行できます。

6
Matthew

時代は変わりました。$sort$group、および$projectとの集約を使用することで、OPが求めていることを達成できると思います。私のシステムでは、usersコレクションからユーザー情報も取得する必要がありました。うまくいけば、これがその周りの質問にも答えることができます。以下は集約パイプです。最後の3つのオブジェクト(並べ替え、グループ、プロジェクト)は、合計数を取得し、ページネーション機能を提供するものです。

db.posts.aggregate([
  { $match: { public: true },
  { $lookup: {
    from: 'users',
    localField: 'userId',
    foreignField: 'userId',
    as: 'userInfo'
  } },
  { $project: {
    postId: 1,
    title: 1,
    description: 1
    updated: 1,
    userInfo: {
      $let: {
        vars: {
          firstUser: {
            $arrayElemAt: ['$userInfo', 0]
          }
        },
        in: {
          username: '$$firstUser.username'
        }
      }
    }
  } },
  { $sort: { updated: -1 } },
  { $group: {
    _id: null,
    postCount: { $sum: 1 },
    posts: {
      $Push: '$$ROOT'
    }
  } },
  { $project: {
    _id: 0,
    postCount: 1,
    posts: {
      $slice: [
        '$posts',
        currentPage ? (currentPage - 1) * RESULTS_PER_PAGE : 0,
        RESULTS_PER_PAGE
      ]
    }
  } }
])
5
TestWell

それはすべて、2つのクエリを実行する必要があるかどうかに関して必要なページネーションの経験に依存します。

すべてのページをリストする必要がありますか、それともページの範囲をリストする必要がありますか?誰も1051ページに行きますか?概念的にそれは実際に何を意味しますか?

ページネーションのパターンには多くのUXがあります- ページネーションの痛みを回避 はさまざまなタイプのページネーションとそのシナリオをカバーし、多くは次のページがあるかどうかを知るためにカウントクエリを必要としません。たとえば、1ページに10個のアイテムを表示し、13個に制限すると、別のページがあるかどうかがわかります。

3
Ross

MongoDBでは、cursor.count()またはlimit()を渡した場合でも、skip()を使用できます。

10個のアイテムを持つdb.collectionがあるとします。

できるよ:

async function getQuery() {
  let query = await db.collection.find({}).skip(5).limit(5); // returns last 5 items in db
  let countTotal = await query.count() // returns 10-- will not take `skip` or `limit` into consideration
  let countWithConstraints = await query.count(true) // returns 5 -- will take into consideration `skip` and `limit`
  return { query, countTotal } 
}
2
Daniel Varela

これは1つのクエリで実行できます。最初にカウントを実行し、その中でlimit()関数を実行します。

Node.jsおよびExpress.jsでは、toArrayの「結果」とともに「count」関数を使用できるように、このように使用する必要があります。

var curFind = db.collection('tasks').find({query});

次に、このように2つの関数を実行できます(一方が他方にネストされています)

curFind.count(function (e, count) {

// Use count here

    curFind.skip(0).limit(10).toArray(function(err, result) {

    // Use result here and count here

    });

});
0
Vibhu

以下のように試してください:

cursor.count(false、function(err、total){console.log( "total"、total)})

core.db.users.find(query, {}, {skip:0, limit:1}, function(err, cursor){
    if(err)
        return callback(err);

    cursor.toArray(function(err, items){
        if(err)
            return callback(err);

        cursor.count(false, function(err, total){
            if(err)
                return callback(err);

            console.log("cursor", total)

            callback(null, {items: items, total:total})
        })
    })
 })
0
surinder singh

cursor.count()を使用して合計カウントを取得できます

 const cursor = await database.collection(collectionName).find(query).skip(offset).limit(limit)
 return {
    data: await cursor.toArray(),
    count: await cursor.count() // this will give count of all the documents before .skip() and limit()
 };
0
Suraj Chandola

ここで回答したようにlimit()を使用してcount()の影響なしに合計結果サイズを取得することは可能ですisMongoDBで結果を制限しますが、まだフルカウントを取得していますか?

ドキュメントによると、count()を呼び出すときに制限/ページネーションを考慮するかどうかを制御することもできます。 https://docs.mongodb.com/manual/reference/method/cursor.count/ #cursor.count

編集:他の場所に書かれているものとは対照的に、ドキュメントには、「操作はクエリを実行せず、クエリによって返される結果をカウントする」と明確に述べられています。私の理解では、これは1つのクエリのみが実行されることを意味します。

例:

> db.createCollection("test")
{ "ok" : 1 }

> db.test.insert([{name: "first"}, {name: "second"}, {name: "third"}, 
{name: "forth"}, {name: "fifth"}])
BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 5,
    "nUpserted" : 0,
    "nMatched" : 0,
    "nModified" : 0,
    "nRemoved" : 0,
    "upserted" : [ ]
})

> db.test.find()
{ "_id" : ObjectId("58ff00918f5e60ff211521c5"), "name" : "first" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c6"), "name" : "second" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c7"), "name" : "third" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c8"), "name" : "forth" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c9"), "name" : "fifth" }

> db.test.count()
5

> var result = db.test.find().limit(3)
> result
{ "_id" : ObjectId("58ff00918f5e60ff211521c5"), "name" : "first" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c6"), "name" : "second" }
{ "_id" : ObjectId("58ff00918f5e60ff211521c7"), "name" : "third" }

> result.count()
5 (total result size of the query without limit)

> result.count(1)
3 (result size with limit(3) taken into account)
0
mrechtien