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


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": [
      {...},
      {...},
      {...},
    ]
  }
]

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}]
  }
]

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 } } }
] );

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

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

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

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
            });
          }
      });
    }
  });
});

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
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) => {})

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
}

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.



0

Jika Anda tidak ingin mengelompokkan, gunakan metode berikut:

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


Saya pikir orang yang mengajukan pertanyaan memang ingin berkelompok, berdasarkan subjeknya.
mjaggard
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.