MongoDB集計:合計レコード数を取得するにはどうすればよいですか?


102

mongodbからレコードを取得するために集計を使用しました。

$result = $collection->aggregate(array(
  array('$match' => $document),
  array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'),  'views' => array('$sum' => 1))),
  array('$sort' => $sort),
  array('$skip' => $skip),
  array('$limit' => $limit),
));

このクエリを無制限に実行すると、10レコードがフェッチされます。ただし、制限を2にしたいので、合計レコード数を取得します。集計をどのように実行できますか?私にアドバイスしてください。ありがとう


2つしかない場合、結果はどのようになりますか?
WiredPrairie、2013

$ facetをご覧ください。これは、stackoverflow.com
questions / 61812361 /…に

回答:


100

これは、1つのクエリでページ分割された結果と結果の総数を同時に取得するための最も一般的な質問の1つです。最終的にそれを達成したときの気持ちを説明することはできません。

$result = $collection->aggregate(array(
  array('$match' => $document),
  array('$group' => array('_id' => '$book_id', 'date' => array('$max' => '$book_viewed'),  'views' => array('$sum' => 1))),
  array('$sort' => $sort),

// get total, AND preserve the results
  array('$group' => array('_id' => null, 'total' => array( '$sum' => 1 ), 'results' => array( '$push' => '$$ROOT' ) ),
// apply limit and offset
  array('$project' => array( 'total' => 1, 'results' => array( '$slice' => array( '$results', $skip, $length ) ) ) )
))

結果は次のようになります。

[
  {
    "_id": null,
    "total": ...,
    "results": [
      {...},
      {...},
      {...},
    ]
  }
]

8
このドキュメント:docs.mongodb.com/v3.2/reference/operator/aggregation/group/… ...このアプローチでは、ページ分割されていない結果セット全体が16MBに収まる必要があることに注意してください。
btown 2016年

7
これは純金です!私は地獄を通り抜けてこの仕事をしようとしていました。
Henrique Miranda

4
みんなありがとう !Iジュスト必要性{ $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}(後に挿入{$group:{}}回数合計見つけるため。
Liberateur

1
結果セットにどのように制限を適用しますか?結果はネストされた配列になりました
valen

@valenコードの最後の行を見ることができます "'results' => array( '$ slice' => array( '$ results'、$ skip、$ length))"ここで制限を適用して
パラメーター

83

v.3.4(私は思う)以降、MongoDBには、 ' facet ' という名前の新しい集約パイプライン演算子があります。

同じ入力ドキュメントのセットの単一ステージ内で複数の集約パイプラインを処理します。各サブパイプラインには、出力ドキュメント内に独自のフィールドがあり、その結果はドキュメントの配列として保存されます。

この特定のケースでは、これは次のようなことができることを意味します。

$result = $collection->aggregate([
  { ...execute queries, group, sort... },
  { ...execute queries, group, sort... },
  { ...execute queries, group, sort... },
  $facet: {
    paginatedResults: [{ $skip: skipPage }, { $limit: perPage }],
    totalCount: [
      {
        $count: 'count'
      }
    ]
  }
]);

結果は次のようになります(元の合計結果が100の場合):

[
  {
    "paginatedResults":[{...},{...},{...}, ...],
    "totalCount":[{"count":100}]
  }
]

13
これは問題なく機能します。3.4以降、これは受け入れられる答えになるはずです
Adam Reis

配列の結果を単純な2つのフィールドオブジェクトに変換するには、別のオブジェクトが必要$projectですか?
SerG

1
これは今や受け入れられた答えでなければなりません。魅力のように働いた。
Arootin Aghazaryan

9
これは、今日受け入れられた答えであるはずです。ただし、$ facetでページングを使用すると、パフォーマンスの問題が見つかりました。投票されたもう1つの回答にも、$ sliceに関するパフォーマンスの問題があります。パイプラインで$ skipと$ limitを使用し、countを個別に呼び出す方がよいことがわかりました。かなり大きなデータセットに対してこれをテストしました。
Jpepper

59

これを使用して、結果のコレクションの総数を見つけます。

db.collection.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );

3
ありがとう。しかし、私はコーディングで「ビュー」を使用して、対応するグループカウントのカウントを取得しています(つまり、グループ1 => 2レコード、グループ3 => 5レコードなど)。レコード数を取得したい(つまり、合計120レコード)。希望あなたは...理解
user2987836

34

toArray関数を使用して、その長さを合計レコード数として取得できます。

db.CollectionName.aggregate([....]).toArray().length

1
これは「適切な」ソリューションとしては機能しないかもしれませんが、何かをデバッグするのに役立ちました。100%のソリューションでなくても機能します。
ヨハンマルクス

3
これは実際の解決策ではありません。
FurkanBaşaran19年

1
TypeError: Parent.aggregate(...).toArray is not a functionこれは私がこのソリューションで与えたエラーです。
Mohammad Hossein Shojaeinia

ありがとう。これは私が探していたものです。
skvp

これにより、すべての集計データがフェッチされ、その配列の長さが返されます。良い実践ではない。代わりに、集約パイプラインに{$ count: 'count'}を追加できます
Aslam Shaik

19

$ count集計パイプラインステージを使用して、合計ドキュメント数を取得します。

クエリ:

db.collection.aggregate(
  [
    {
      $match: {
        ...
      }
    },
    {
      $group: {
        ...
      }
    },
    {
      $count: "totalCount"
    }
  ]
)

結果:

{
   "totalCount" : Number of records (some integer value)
}

これは魅力のように機能しますが、パフォーマンスの点で優れていますか?
ana.arede

きれいな溶液。ありがとう
skvp

13

私はこのようにしました:

db.collection.aggregate([
     { $match : { score : { $gt : 70, $lte : 90 } } },
     { $group: { _id: null, count: { $sum: 1 } } }
] ).map(function(record, index){
        print(index);
 });

集計は配列を返すので、ループして最終的なインデックスを取得します。

それを行う他の方法は次のとおりです:

var count = 0 ;
db.collection.aggregate([
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] ).map(function(record, index){
        count++
 }); 
print(count);

fwiw var宣言もmap呼び出しも必要ありません。最初の例の最初の3行で十分です。
Madbreaks

7

@Divergentによって提供されるソリューションは機能しますが、私の経験では2つのクエリを使用する方が良いです。

  1. 最初にフィルタリングし、IDでグループ化して、フィルタリングされた要素の数を取得します。ここではフィルタリングしないでください。これは不要です。
  2. フィルター、並べ替え、ページ付けを行う2番目のクエリ。

$$ ROOTをプッシュし、$ sliceを使用するソリューションは、大規模なコレクションの場合、16MBのドキュメントメモリ制限に達します。また、大きなコレクションの場合、2つのクエリを一緒に実行すると、$$ ROOTプッシュを使用したクエリよりも高速に実行されるようです。それらを並行して実行することもできるため、2つのクエリの遅い方(おそらくソートするクエリ)によってのみ制限されます。

私は2つのクエリと集約フレームワークを使用してこの解決策を解決しました(注-この例ではnode.jsを使用していますが、考え方は同じです)。

var aggregation = [
  {
    // If you can match fields at the begining, match as many as early as possible.
    $match: {...}
  },
  {
    // Projection.
    $project: {...}
  },
  {
    // Some things you can match only after projection or grouping, so do it now.
    $match: {...}
  }
];


// Copy filtering elements from the pipeline - this is the same for both counting number of fileter elements and for pagination queries.
var aggregationPaginated = aggregation.slice(0);

// Count filtered elements.
aggregation.push(
  {
    $group: {
      _id: null,
      count: { $sum: 1 }
    }
  }
);

// Sort in pagination query.
aggregationPaginated.push(
  {
    $sort: sorting
  }
);

// Paginate.
aggregationPaginated.push(
  {
    $limit: skip + length
  },
  {
    $skip: skip
  }
);

// I use mongoose.

// Get total count.
model.count(function(errCount, totalCount) {
  // Count filtered.
  model.aggregate(aggregation)
  .allowDiskUse(true)
  .exec(
  function(errFind, documents) {
    if (errFind) {
      // Errors.
      res.status(503);
      return res.json({
        'success': false,
        'response': 'err_counting'
      });
    }
    else {
      // Number of filtered elements.
      var numFiltered = documents[0].count;

      // Filter, sort and pagiante.
      model.request.aggregate(aggregationPaginated)
      .allowDiskUse(true)
      .exec(
        function(errFindP, documentsP) {
          if (errFindP) {
            // Errors.
            res.status(503);
            return res.json({
              'success': false,
              'response': 'err_pagination'
            });
          }
          else {
            return res.json({
              'success': true,
              'recordsTotal': totalCount,
              'recordsFiltered': numFiltered,
              'response': documentsP
            });
          }
      });
    }
  });
});

5
//const total_count = await User.find(query).countDocuments();
//const users = await User.find(query).skip(+offset).limit(+limit).sort({[sort]: order}).select('-password');
const result = await User.aggregate([
  {$match : query},
  {$sort: {[sort]:order}},
  {$project: {password: 0, avatarData: 0, tokens: 0}},
  {$facet:{
      users: [{ $skip: +offset }, { $limit: +limit}],
      totalCount: [
        {
          $count: 'count'
        }
      ]
    }}
  ]);
console.log(JSON.stringify(result));
console.log(result[0]);
return res.status(200).json({users: result[0].users, total_count: result[0].totalCount[0].count});

1
通常、コードの回答とともに説明テキストを含めることをお勧めします。

3

これは複数の一致条件で機能する可能性があります

            const query = [
                {
                    $facet: {
                    cancelled: [
                        { $match: { orderStatus: 'Cancelled' } },
                        { $count: 'cancelled' }
                    ],
                    pending: [
                        { $match: { orderStatus: 'Pending' } },
                        { $count: 'pending' }
                    ],
                    total: [
                        { $match: { isActive: true } },
                        { $count: 'total' }
                    ]
                    }
                },
                {
                    $project: {
                    cancelled: { $arrayElemAt: ['$cancelled.cancelled', 0] },
                    pending: { $arrayElemAt: ['$pending.pending', 0] },
                    total: { $arrayElemAt: ['$total.total', 0] }
                    }
                }
                ]
                Order.aggregate(query, (error, findRes) => {})

2

集計を適用した後、絶対合計数が必要でした。これは私のために働きました:

db.mycollection.aggregate([
    {
        $group: { 
            _id: { field1: "$field1", field2: "$field2" },
        }
    },
    { 
        $group: { 
            _id: null, count: { $sum: 1 } 
        } 
    }
])

結果:

{
    "_id" : null,
    "count" : 57.0
}

2

MongoDBの集計中にレコードの総数を取得するいくつかの方法を次に示します。


  • 使用$count

    db.collection.aggregate([
       // Other stages here
       { $count: "Total" }
    ])

    1000レコードを取得する場合、これは平均2ミリ秒かかり、最速の方法です。


  • 使用.toArray()

    db.collection.aggregate([...]).toArray().length

    1000レコードを取得する場合、平均で18ミリ秒かかります。


  • 使用.itcount()

    db.collection.aggregate([...]).itcount()

    1000件のレコードを取得するには、平均で14ミリ秒かかります。


0

すみませんが、2つのクエリが必要だと思います。1つは合計ビュー用、もう1つはグループ化されたレコード用です。

あなたはこの答えを見つけることができます


おかげで..私はそう思います..しかし、集約のオプションはありません.. :(
user2987836

1
私は同じような状況に遭遇しました。2つのクエリを実行する以外に答えはありませんでした。:( stackoverflow.com/questions/20113731/...
astroanu

0

グループ化したくない場合は、次の方法を使用します。

db.collection.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $count: 'count' } ] );


質問をする人は主題に基づいてグループ化したいと思います。
mjaggard
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.