Agregasi MongoDB: Bagaimana cara menghitung total catatan?

105

Saya telah menggunakan agregasi untuk mengambil catatan dari 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),
));

Jika saya menjalankan kueri ini tanpa batas, maka 10 catatan akan diambil. Tapi saya ingin membatasi 2. Jadi saya ingin menghitung total catatan. Bagaimana saya bisa melakukan dengan agregasi? Mohon saran saya. Terima kasih

pengguna2987836
sumber
Akan seperti apa hasilnya jika hanya ada 2?
WiredPrairie
Lihatlah $ facet. Ini dapat membantu stackoverflow.com/questions/61812361/…
Soham

Jawaban:

104

Ini adalah salah satu pertanyaan yang paling umum ditanyakan untuk mendapatkan hasil paginasi dan jumlah total hasil secara bersamaan dalam satu kueri. Saya tidak bisa menjelaskan bagaimana perasaan saya ketika akhirnya saya mencapainya LOL.

$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 ) ) ) )
))

Hasilnya akan terlihat seperti ini:

[
  {
    "_id": null,
    "total": ...,
    "results": [
      {...},
      {...},
      {...},
    ]
  }
]
Anurag pareek
sumber
8
Dokumentasi tentang ini: docs.mongodb.com/v3.2/reference/operator/aggregation/group/… ... perhatikan bahwa dengan pendekatan ini, seluruh kumpulan hasil non-paginasi harus muat dalam 16 MB.
btown
8
Ini emas murni! Aku akan melalui neraka mencoba membuat ini berhasil.
Henrique Miranda
4
Terimakasih teman ! Saya hanya perlu { $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}(masukkan setelah {$group:{}}untuk menghitung jumlah total.
Liberateur
1
Bagaimana Anda menerapkan batas ke set hasil? Hasilnya sekarang berupa larik bersarang
valen
2
Hidup saya sudah lengkap sekarang, saya bisa mati bahagia
Jack
91

Sejak v.3.4 (menurut saya) MongoDB sekarang memiliki operator pipeline agregasi baru bernama ' facet ' yang dengan kata-katanya sendiri:

Memproses beberapa jalur agregasi dalam satu tahap pada kumpulan dokumen masukan yang sama. Setiap sub-pipeline memiliki kolomnya sendiri di dokumen keluaran yang hasilnya disimpan sebagai array dokumen.

Dalam kasus khusus ini, ini berarti seseorang dapat melakukan sesuatu seperti ini:

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

Hasilnya adalah (dengan untuk contoh hasil total 100):

[
  {
    "paginatedResults":[{...},{...},{...}, ...],
    "totalCount":[{"count":100}]
  }
]
user3658510
sumber
13
Ini bekerja dengan baik, pada 3.4 ini harus menjadi jawaban yang diterima
Adam Reis
Untuk mengubah hasil yang begitu larik menjadi objek dua bidang sederhana, saya butuh yang lain $project?
SerG
1
ini sekarang harus menjadi jawaban yang diterima. bekerja seperti pesona.
Arootin Aghazaryan
9
Ini harus menjadi jawaban yang diterima hari ini. Namun, saya menemukan masalah kinerja saat menggunakan paging dengan $ facet. Jawaban lain yang dipilih juga memiliki masalah performa dengan $ slice. Saya merasa lebih baik untuk $ lewati dan $ batas dalam pipa dan membuat panggilan terpisah untuk menghitung. Saya menguji ini terhadap kumpulan data yang cukup besar.
Jpepper
59

Gunakan ini untuk menemukan jumlah total dalam koleksi yang dihasilkan.

db.collection.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
Vishal Ranapariya
sumber
3
Terima kasih. Tapi, saya telah menggunakan "tampilan" dalam pengkodean saya untuk mendapatkan hitungan jumlah grup yang sesuai (yaitu, grup 1 => 2 rekaman, grup 3 => 5 rekaman & seterusnya). Saya ingin menghitung catatan (yaitu, total: 120 catatan). Harap Anda mengerti ..
pengguna2987836
37

Anda bisa menggunakan fungsi toArray dan kemudian mendapatkan panjangnya untuk jumlah catatan total.

db.CollectionName.aggregate([....]).toArray().length
Ankit Arya
sumber
1
Meskipun ini mungkin tidak berfungsi sebagai solusi yang "tepat", ini membantu saya men-debug sesuatu - ini berhasil, meskipun itu bukan solusi 100%.
Johann Marx
3
Ini bukanlah solusi nyata.
Furkan Başaran
1
TypeError: Parent.aggregate(...).toArray is not a functionini adalah kesalahan yang saya berikan dengan solusi ini.
Mohammad Hossein Shojaeinia
Terima kasih. Inilah yang saya cari.
skvp
Ini akan mengambil semua data teragregasi lalu mengembalikan panjang larik itu. bukan praktik yang baik. alih-alih Anda dapat menambahkan {$ count: 'count'} dalam pipa agregasi
Aslam Shaik
21

Gunakan tahap pipeline agregasi $ count untuk mendapatkan jumlah dokumen total:

Pertanyaan:

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

Hasil:

{
   "totalCount" : Number of records (some integer value)
}
cnsnaveen.dll
sumber
Ini berfungsi seperti pesona, tetapi dari segi kinerja, apakah itu bagus?
ana.arede
Solusi bersih. Terima kasih
skvp
13

Saya melakukannya dengan cara ini:

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

Agregat akan mengembalikan array jadi putar saja dan dapatkan indeks akhir.

Dan cara lain untuk melakukannya adalah:

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);
Orang gila
sumber
fwiw Anda tidak memerlukan vardeklarasi atau mappanggilan. 3 baris pertama dari contoh pertama Anda sudah cukup.
Madbreaks
7

Solusi yang diberikan oleh @Divergent memang berfungsi, tetapi menurut pengalaman saya, lebih baik memiliki 2 pertanyaan:

  1. Pertama untuk memfilter dan kemudian mengelompokkan berdasarkan ID untuk mendapatkan jumlah elemen yang difilter. Jangan memfilter di sini, itu tidak perlu.
  2. Kueri kedua yang memfilter, mengurutkan, dan memberi nomor halaman.

Solusi dengan mendorong $$ ROOT dan menggunakan $ slice mengalami batasan memori dokumen sebesar 16MB untuk koleksi besar. Selain itu, untuk koleksi besar, dua kueri bersama-sama tampaknya berjalan lebih cepat daripada kueri dengan dorongan $$ ROOT. Anda juga dapat menjalankannya secara paralel, jadi Anda hanya dibatasi oleh yang lebih lambat dari dua kueri (mungkin yang menyortir).

Saya telah menyelesaikan solusi ini menggunakan 2 kueri dan kerangka agregasi (catatan - saya menggunakan node.js dalam contoh ini, tetapi idenya sama):

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
            });
          }
      });
    }
  });
});
Filip Voska
sumber
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});
Harpal Singh
sumber
1
Biasanya merupakan praktik yang baik untuk memasukkan teks penjelasan bersama dengan kode jawaban.
3

Ini bisa berfungsi untuk beberapa kondisi pertandingan

            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) => {})
Rohit Parte
sumber
2

Saya membutuhkan jumlah total absolut setelah menerapkan agregasi. Ini berhasil untuk saya:

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

Hasil:

{
    "_id" : null,
    "count" : 57.0
}
miqrc
sumber
2

Berikut beberapa cara untuk mendapatkan jumlah catatan saat melakukan Agregasi MongoDB:


  • Menggunakan $count:

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

    Untuk mendapatkan 1000 catatan, ini membutuhkan waktu rata-rata 2 md dan merupakan cara tercepat.


  • Menggunakan .toArray():

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

    Untuk mendapatkan 1000 record, dibutuhkan waktu rata-rata 18 ms.


  • Menggunakan .itcount():

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

    Untuk mendapatkan 1000 record, dibutuhkan waktu rata-rata 14 ms.

palaѕн
sumber
0

Maaf, tapi saya rasa Anda perlu dua pertanyaan. Satu untuk tampilan total dan satu lagi untuk rekaman yang dikelompokkan.

Anda dapat menemukan jawaban yang berguna ini

rubenfa.dll
sumber
Terima kasih..Saya kira begitu..Tapi, tidak ada opsi dengan agregasi .. :(
user2987836
1
saya berlari ke situasi yang sama. Tidak ada jawaban selain melakukan 2 querys. :( stackoverflow.com/questions/20113731/…
astroanu
0

Jika Anda tidak ingin mengelompokkan, gunakan metode berikut:

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

Rajan Sharma
sumber
Saya pikir orang yang mengajukan pertanyaan memang ingin berkelompok, berdasarkan subjeknya.
mjaggard