マングースの制限/オフセットおよびカウントクエリ


84

クエリのパフォーマンスに関して少し奇妙なことです...ドキュメントの総数を実行し、制限およびオフセットできる結果セットを返すこともできるクエリを実行する必要があります。

したがって、合計57のドキュメントがあり、ユーザーは10のドキュメントを20でオフセットしたいと考えています。

これを行うには2つの方法が考えられます。1つは57個のドキュメントすべて(配列として返される)をクエリし、次にarray.sliceを使用して必要なドキュメントを返すことです。2番目のオプションは、2つのクエリを実行することです。最初のクエリはmongoのネイティブの「count」メソッドを使用し、次にmongoのネイティブの$ limitおよび$ skipアグリゲーターを使用して2番目のクエリを実行します。

どちらがより適切にスケーリングすると思いますか?すべてを1つのクエリで実行しますか、それとも2つの別々のクエリを実行しますか?

編集:

// 1 query
var limit = 10;
var offset = 20;

Animals.find({}, function (err, animals) {
    if (err) {
        return next(err);
    }

    res.send({count: animals.length, animals: animals.slice(offset, limit + offset)});
});


// 2 queries
Animals.find({}, {limit:10, skip:20} function (err, animals) {            
    if (err) {
        return next(err);
    }

    Animals.count({}, function (err, count) {
        if (err) {
            return next(err);
        }

        res.send({count: count, animals: animals});
    });
});

しかし私は、デフォルトのマングースが不明だcount()PHPの関数はなりませんlimitか、skip考慮にこれだけの限界の1つのクエリを実行するに告げなければスキップして、数を取得することは、おそらくここで最もパフォーマンスの高いソリューションを与える必要があります。しかし、現在そこにあるものを数えるために2つのクエリを実行しない場合、57のドキュメントがあることをどのように知ることができますか?決して変わらない静的な番号はありますか?そうでない場合は、スキップと制限の両方を実行してからカウントする必要があります。
Sammaye 2012

申し訳ありませんが、Mongoのネイティブカウント方法の使用について話していましたdb.collection.find(<query>).count();
leepowell 2012

すみません、私でした。あなたの質問を読み間違えました。うーん、実際にはどちらが良いかわかりませんが、結果セットは常に57ドキュメントのように本当に低くなりますか?その場合、クライアント側のスライスのパフォーマンスが1ミリ秒高くなる可能性があります。
Sammaye 2012

元の質問に例を追加しました。データが10,000以上になることはないと思いますが、可能性はあります。
leepowell 2012

10kレコードでは、JSのメモリ処理のパフォーマンスがMongoDBの機能よりも低いことわかりますcount()count()MongoDBの機能は比較的遅いですが、それでも大きなセットでのほとんどのクライアント側のバリエーションとほぼ同じくらい高速であり、おそらくここでクライアント側を数えるよりも高速である可能性があります。しかし、その部分はあなた自身のテストに主観的です。以前に10kの長さの配列を簡単に数えたことがあるので、クライアント側の方が速いかもしれません。10kの要素で言うのは非常に難しいです。
Sammaye 2012

回答:


129

2つのクエリを使用することをお勧めします。

  1. db.collection.count()アイテムの総数を返します。この値はMongoのどこかに保存されており、計算されません。

  2. db.collection.find().skip(20).limit(10)ここでは、あるフィールドによるソートを使用できると想定しているので、このフィールドにインデックスを追加することを忘れないでください。このクエリも高速になります。

すべてのアイテムをクエリするのではなく、スキップアンドテイクを実行するよりも、後でビッグデータがあるときにデータの転送と処理で問題が発生する原因になると思います。


1
私が書いているのは、何の注意も払わない単なるコメントです.skip()が、コレクションの先頭に移動し、のパラメーターで指定された値に到達するため、CPUにとって命令が重いと聞きました.skip()。それは大きなコレクションに本当の影響を与えることができます!しかし、.skip()とにかくどちらを使用するのが最も重いのか、コレクション全体を取得してJSでトリミングするのかはわかりません...どう思いますか?
Zachary Dahan 2015

2
@Stuffixを使用することについて同じ懸念を聞いたことがあり.skip()ます。この回答はそれに触れており、日付フィールドにフィルターを使用することをお勧めします。これは.skip().take()メソッドで使用できます。これは良い考えのようです。ただし、ドキュメントの総数を取得する方法に関するこのOPの質問に問題があります。のパフォーマンスへの影響に対抗するためにフィルターが使用されている場合、.skip()正確なカウントを行うにはどうすればよいですか?データベースに保存されているカウントは、フィルタリングされたデータセットを反映しません。
Michael Leanos 2016年

こんにちは@MichaelLeanos、私は同じ問題に直面しています:つまり、ドキュメントの総数を取得する方法。フィルタを使用する場合、正確なカウントを行うにはどうすればよいですか?これに対する解決策はありましたか?
virsha 2017

@virsha、cursor.count()フィルタリングされたドキュメントの数を返すために使用します(クエリは実行されず、一致したドキュメントの数が返されます)。フィルターと注文のプロパティにインデックスが付けられていることを確認してください。
user854301 2017

@virshaを使用するcursor.count()と、@ user854301が指摘したように機能するはずです。しかし、私がやったことは、/api/my-colllection/statsMongooseのdb.collection.stats機能を使用してコレクションのさまざまな統計を返すために使用したエンドポイントをAPI()に追加することでした。これはフロントエンドにのみ必要だったので、サーバー側のページ付けとは関係なく、エンドポイントにその情報を返すように問い合わせました。
Michael Leanos 2017

19

2つの個別のクエリを使用する代わりaggregate()に、1つのクエリで使用できます。

集計「$ facet」をより迅速にフェッチでき、合計数スキップと制限のあるデータ

    db.collection.aggregate([

      //{$sort: {...}}

      //{$match:{...}}

      {$facet:{

        "stage1" : [ {"$group": {_id:null, count:{$sum:1}}} ],

        "stage2" : [ { "$skip": 0}, {"$limit": 2} ]
  
      }},
     
     {$unwind: "$stage1"},
  
      //output projection
     {$project:{
        count: "$stage1.count",
        data: "$stage2"
     }}

 ]);

次のように出力します:-

[{
     count: 50,
     data: [
        {...},
        {...}
      ]
 }]

また、https://docs.mongodb.com/manual/reference/operator/aggregation/facet/もご覧ください。


2

この問題に自分で取り組む必要があった後、user854301の回答に基づいて構築したいと思います。

Mongoose ^ 4.13.8と呼ばれる関数を使用することができました。toConstructor()これにより、フィルターが適用されたときにクエリを複数回作成する必要がなくなりました。この機能は古いバージョンでも利用できることは知っていますが、これを確認するには、Mongooseのドキュメントを確認する必要があります。

以下はBluebirdの約束を使用しています。

let schema = Query.find({ name: 'bloggs', age: { $gt: 30 } });

// save the query as a 'template'
let query = schema.toConstructor();

return Promise.join(
    schema.count().exec(),
    query().limit(limit).skip(skip).exec(),

    function (total, data) {
        return { data: data, total: total }
    }
);

これで、カウントクエリは一致した合計レコードを返し、返されるデータは合計レコードのサブセットになります。

クエリを構成するquery ()の前後の()に注意してください。



0
db.collection_name.aggregate([
    { '$match'    : { } },
    { '$sort'     : { '_id' : -1 } },
    { '$facet'    : {
        metadata: [ { $count: "total" } ],
        data: [ { $skip: 1 }, { $limit: 10 },{ '$project' : {"_id":0} } ] // add projection here wish you re-shape the docs
    } }
] )

2つのクエリを使用して合計数を見つけ、一致したレコードをスキップする代わりに。
$ facetは、最適化された最良の方法です。

  1. レコードと一致する
  2. total_countを検索します
  3. レコードをスキップする
  4. また、クエリのニーズに応じてデータの形状を変更することもできます。

1
他の人がそれから学ぶことができるようにあなたの答えにいくつかの説明を追加してください
ニコハース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.