MongoDB Aggregation: Toplam kayıt sayısı nasıl alınır?


107

Mongodb'den kayıtları almak için toplama kullandım.

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

Bu sorguyu sınırsız olarak yürütürsem, 10 kayıt getirilecek. Ama limiti 2 olarak tutmak istiyorum. Bu yüzden toplam kayıt sayısını almak istiyorum. Toplama ile nasıl yapabilirim? Lütfen bana tavsiye verin. Teşekkürler


Yalnızca 2 olsaydı sonuçlar nasıl görünürdü?
WiredPrairie

$ Facet'e bir göz atın Bu, stackoverflow.com/questions/61812361/…
Soham

Yanıtlar:


105

Bu, sayfalara ayrılmış sonucu ve tek bir sorguda aynı anda toplam sonuç sayısını elde etmek için en sık sorulan sorulardan biridir. Nihayet LOL'a ulaştığımda nasıl hissettiğimi açıklayamam.

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

Sonuç şunun gibi görünecek:

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

8
Bununla ilgili belgeler: docs.mongodb.com/v3.2/reference/operator/aggregation/group/… ... bu yaklaşımla, sayfalandırılmamış sonuç kümesinin tamamının 16MB'ye sığması gerektiğini unutmayın.
btown

8
Bu saf altın! Bu işi yapmak için cehenneme gidecektim.
Henrique Miranda

4
Teşekkürler dostum! Sadece ihtiyacım var { $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}( {$group:{}}sayı toplam bulmak için sonra ekleyin .
Liberateur

1
Sonuç kümesine nasıl limit uygularsınız? Sonuçlar artık iç içe geçmiş bir dizi
valen

2
Hayatım şimdi tamam, mutlu ölebilirim
Jack

94

V.3.4'ten bu yana (sanırım) MongoDB artık kendi sözleriyle ' facet ' adında yeni bir toplama işlem hattı operatörüne sahip:

Aynı girdi belgeleri kümesindeki tek bir aşamada birden çok toplama işlem hattını işler. Her bir alt boru hattının, sonuçlarının bir belge dizisi olarak saklandığı çıktı belgesinde kendi alanı vardır.

Bu özel durumda, bu, şu gibi bir şey yapılabileceği anlamına gelir:

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

Sonuç şu şekilde olacaktır (ör. 100 toplam sonuç için):

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

13
Bu harika çalışıyor,
Adam Reis

Bu kadar dizi içeren sonucu basit iki alan nesnesine dönüştürmek için başka birine ihtiyacım var $project?
SerG

1
bu şimdi kabul edilen cevap olmalıdır. çekicilik gibi çalıştı.
Arootin Aghazaryan

9
Bugün kabul edilen cevap bu olmalıdır. Ancak, $ facet ile disk belleği kullanırken performans sorunları buldum. Diğer yukarı oylanan cevapta da $ slice ile performans sorunları var. Ardışık düzen içinde $ atlama ve $ limit yapmayı ve sayım için ayrı bir çağrı yapmayı daha iyi buldum. Bunu oldukça büyük veri kümelerine karşı test ettim.
Jpepper

60

Elde edilen koleksiyondaki toplam sayıyı bulmak için bunu kullanın.

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

3
Teşekkürler. Ancak, karşılık gelen grup sayısının sayısını elde etmek için kodlamamda "görünümler" kullandım (yani, grup 1 => 2 kayıt, grup 3 => 5 kayıt vb.). Kayıt sayısını (yani toplam: 120 kayıt) almak istiyorum. Umarım
anlamışsındır

38

ToArray işlevini kullanabilir ve ardından toplam kayıt sayısı için uzunluğunu öğrenebilirsiniz.

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

1
Bu "uygun" bir çözüm olarak işe yaramasa da, bir şeyde hata ayıklamama yardımcı oldu -% 100 bir çözüm olmasa bile işe yarıyor.
Johann Marx

3
Bu gerçek bir çözüm değil.
Furkan Başaran

1
TypeError: Parent.aggregate(...).toArray is not a functionbu, bu çözümle verdiğim hata.
Mohammad Hossein Shojaeinia

Teşekkürler. Aradığım buydu.
skvp

1
Bu, toplanan tüm verileri alır ve ardından bu dizinin uzunluğunu döndürür. iyi bir uygulama değil. bunun yerine toplama işlem hattına {$ count: 'count'} ekleyebilirsiniz
Aslam Shaik

22

Toplam belge sayısını elde etmek için $ count toplama işlem hattı aşamasını kullanın:

Sorgu :

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

Sonuç:

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

Bu tıpkı bir cazibe gibi çalışıyor, ancak performans açısından iyi mi?
ana.arede

Temiz çözüm. Teşekkürler
skvp

13

Ben şu şekilde yaptım:

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

Toplam, diziyi döndürecektir, bu yüzden onu döngüye sok ve son dizini al.

Ve bunu yapmanın başka bir yolu:

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 varbeyana veya mapçağrıya ihtiyacınız yok . İlk örneğinizin ilk 3 satırı yeterlidir.
Madbreaks

7

@Divergent tarafından sağlanan çözüm işe yarıyor, ancak deneyimlerime göre 2 sorgu olması daha iyi:

  1. Önce filtreleme ve ardından filtrelenen öğelerin sayısını almak için kimliğe göre gruplama için. Burada filtreleme yapmayın, gereksizdir.
  2. Filtreleyen, sıralayan ve sayfalandıran ikinci sorgu.

$$ ROOT'u zorlayarak ve $ dilim kullanarak çözüm, büyük koleksiyonlar için 16MB'lık belge bellek sınırlamasına girer. Ayrıca, büyük koleksiyonlar için, iki sorgu birlikte $$ ROOT zorlamasına göre daha hızlı çalışıyor gibi görünüyor. Bunları paralel olarak da çalıştırabilirsiniz, böylece yalnızca iki sorgudan daha yavaş olanı (muhtemelen sıralayan) sınırlandırırsınız.

Bu çözüme 2 sorgu ve toplama çerçevesi kullanarak karar verdim (not - bu örnekte node.js kullanıyorum, ancak fikir aynı):

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
Kod cevabıyla birlikte açıklayıcı bir metin eklemek genellikle iyi bir uygulamadır.

3

Bu, çoklu maç koşulları için işe yarayabilir

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

3

MongoDB Aggregation yaparken toplam kayıt sayısını almanın bazı yolları:


  • Kullanarak $count:

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

    1000 kayıt almak için bu ortalama 2 ms sürer ve en hızlı yoldur.


  • Kullanarak .toArray():

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

    1000 kayıt almak için bu ortalama 18 ms sürer.


  • Kullanarak .itcount():

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

    1000 kayıt almak için bu ortalama 14 ms sürer.


2

Toplamayı uyguladıktan sonra mutlak toplam sayısına ihtiyacım vardı. Bu benim için çalıştı:

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

Sonuç:

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


0

Gruplamak istemiyorsanız, aşağıdaki yöntemi kullanın:

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


Bence soruyu soran kişi konuya göre gruplaşmak istiyor.
mjaggard
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.