MongoDB Aggregation: จะนับจำนวนระเบียนทั้งหมดได้อย่างไร?


108

ฉันใช้การรวมเพื่อดึงข้อมูลจาก 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

ลองดูที่ $ facet สิ่งนี้อาจช่วยได้stackoverflow.com/questions/61812361/…
Soham

คำตอบ:


106

นี่เป็นคำถามที่ถามบ่อยที่สุดเพื่อให้ได้ผลลัพธ์ที่มีการแบ่งหน้าและจำนวนผลลัพธ์ทั้งหมดพร้อมกันในแบบสอบถามเดียว ฉันไม่สามารถอธิบายได้ว่าฉันรู้สึกอย่างไรเมื่อในที่สุดฉันก็ทำได้ 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 ) ) ) )
))

ผลลัพธ์จะมีลักษณะดังนี้:

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

8
เอกสารเกี่ยวกับเรื่องนี้: docs.mongodb.com/v3.2/reference/operator/aggregation/group/… ... โปรดทราบว่าด้วยวิธีการนี้ชุดผลลัพธ์ที่ไม่มีเลขหน้าทั้งหมดจะต้องมีขนาด 16MB
btown

8
นี่คือทองคำบริสุทธิ์! ฉันกำลังจะผ่านนรกเพื่อพยายามทำงานนี้
Henrique Miranda

4
ขอบคุณครับ! ฉันแค่ต้องการ{ $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}(ใส่หลัง{$group:{}}เพื่อนับจำนวนการค้นหาทั้งหมด
Liberateur

1
คุณใช้ขีด จำกัด กับผลลัพธ์ที่ตั้งไว้ได้อย่างไร? ผลลัพธ์เป็นอาร์เรย์ที่ซ้อนกัน
valen

2
ชีวิตของฉันสมบูรณ์แล้วตอนนี้ฉันสามารถตายได้อย่างมีความสุข
แจ็ค

97

เนื่องจาก 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}]
  }
]

14
สิ่งนี้ใช้งานได้ดีเนื่องจาก 3.4 ควรเป็นคำตอบที่ยอมรับ
Adam Reis

ในการแปลงผลลัพธ์ที่หลากหลายให้เป็นวัตถุสองช่องอย่างง่ายฉันต้องการอีก$projectหรือไม่
SerG

1
ตอนนี้ต้องเป็นคำตอบที่ยอมรับได้ ทำงานอย่างมีเสน่ห์
Arootin Aghazaryan

9
นี่น่าจะเป็นคำตอบที่ได้รับการยอมรับในวันนี้ อย่างไรก็ตามฉันพบปัญหาด้านประสิทธิภาพเมื่อใช้เพจกับ $ facet คำตอบอื่น ๆ ที่ได้รับการโหวตยังมีปัญหาด้านประสิทธิภาพกับ $ slice ฉันพบว่าดีกว่าที่จะ $ ข้ามและ จำกัด $ ในไปป์ไลน์และทำการโทรแยกกันเพื่อนับ ฉันทดสอบสิ่งนี้กับชุดข้อมูลที่ค่อนข้างใหญ่
Jpepper

61

ใช้สิ่งนี้เพื่อค้นหาจำนวนรวมในคอลเล็กชันผลลัพธ์

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

3
ขอบคุณ. แต่ฉันได้ใช้ "มุมมอง" ในการเข้ารหัสของฉันเพื่อรับจำนวนการนับกลุ่มที่เกี่ยวข้อง (เช่นกลุ่ม 1 => 2 ระเบียนกลุ่ม 3 => 5 ระเบียนและอื่น ๆ ) ฉันต้องการรับจำนวนระเบียน (เช่นทั้งหมด: 120 รายการ) หวังว่าคุณจะเข้าใจ ..
user2987836

39

คุณสามารถใช้ฟังก์ชัน toArray จากนั้นรับความยาวสำหรับจำนวนระเบียนทั้งหมด

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

1
แม้ว่าวิธีนี้อาจไม่ได้ผลในฐานะโซลูชันที่ "เหมาะสม" แต่ก็ช่วยฉันแก้ไขข้อบกพร่องบางอย่างได้ - ทำงานได้แม้ว่าจะไม่ใช่วิธีแก้ปัญหา 100% ก็ตาม
Johann Marx

3
นี่ไม่ใช่วิธีแก้ปัญหาที่แท้จริง
Furkan Başaran

1
TypeError: Parent.aggregate(...).toArray is not a functionนี่คือข้อผิดพลาดที่ฉันให้กับโซลูชันนี้
Mohammad Hossein Shojaeinia

ขอบคุณ. นี่คือสิ่งที่ฉันกำลังมองหา
skvp

1
สิ่งนี้จะดึงข้อมูลรวมทั้งหมดจากนั้นส่งกลับความยาวของอาร์เรย์นั้น ไม่ใช่แนวทางปฏิบัติที่ดี คุณสามารถเพิ่ม {$ count: 'count'} ในไปป์ไลน์การรวมได้แทน
Aslam Shaik

22

ใช้ขั้นตอนไปป์ไลน์การรวมจำนวน $ 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. แบบสอบถามที่สองซึ่งกรองเรียงลำดับและเลขหน้า

การแก้ปัญหาด้วยการกด $$ ROOT และการใช้ $ slice จะทำงานในข้อ จำกัด หน่วยความจำเอกสารที่ 16MB สำหรับคอลเล็กชันขนาดใหญ่ นอกจากนี้สำหรับคอลเลกชันขนาดใหญ่สองการสืบค้นร่วมกันดูเหมือนว่าจะทำงานได้เร็วกว่าคำค้นหาที่มีการกด $$ ROOT คุณสามารถเรียกใช้แบบขนานได้เช่นกันดังนั้นคุณจึงถูก จำกัด ด้วยคำค้นหาที่ช้าลงจากสองคำค้นหาเท่านั้น (อาจเป็นคำค้นหาที่เรียงลำดับ)

ฉันได้ตัดสินด้วยโซลูชันนี้โดยใช้ 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
โดยปกติแล้วการใส่ข้อความอธิบายพร้อมกับคำตอบของรหัสถือเป็นแนวทางปฏิบัติที่ดี

4

ต่อไปนี้เป็นวิธีการรับจำนวนเร็กคอร์ดทั้งหมดในขณะที่ทำการ MongoDB Aggregation:


  • ใช้$count:

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

    สำหรับการรับ 1,000 ระเบียนจะใช้เวลาโดยเฉลี่ย 2 ms และเป็นวิธีที่เร็วที่สุด


  • ใช้.toArray():

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

    สำหรับการรับ 1,000 ระเบียนจะใช้เวลาโดยเฉลี่ย 18 ms


  • ใช้.itcount():

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

    สำหรับการรับ 1,000 ระเบียนจะใช้เวลาโดยเฉลี่ย 14 ms


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
}

0

ขออภัยฉันคิดว่าคุณต้องมีคำถามสองข้อ หนึ่งสำหรับการดูทั้งหมดและอีกรายการหนึ่งสำหรับเรกคอร์ดที่จัดกลุ่ม

คำตอบนี้มีประโยชน์


ขอบคุณ.. ฉันคิดอย่างนั้น.. แต่ไม่มีตัวเลือกสำหรับการรวม .. :(
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.