MongoDB Aggregation: Làm thế nào để có được tổng số bản ghi?


102

Tôi đã sử dụng tính năng tổng hợp để tìm nạp các bản ghi từ 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),
));

Nếu tôi thực hiện truy vấn này mà không có giới hạn thì 10 bản ghi sẽ được tìm nạp. Nhưng tôi muốn giữ giới hạn là 2. Vì vậy, tôi muốn tính tổng số bản ghi. Làm thế nào tôi có thể làm với tổng hợp? Xin hãy tư vấn cho tôi. Cảm ơn


Kết quả sẽ như thế nào nếu chỉ có 2?
WiredPrairie

Hãy xem $ facet Điều này có thể giúp ích cho stackoverflow.com/questions/61812361/…
Soham

Câu trả lời:


100

Đây là một trong những câu hỏi thường gặp nhất để lấy kết quả được phân trang và tổng số kết quả đồng thời trong một truy vấn. Tôi không thể giải thích cảm giác của mình khi cuối cùng đã đạt được nó 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 ) ) ) )
))

Kết quả sẽ giống như sau:

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

8
Tài liệu về điều này: docs.mongodb.com/v3.2/reference/operator/aggregation/group/… ... lưu ý rằng với cách tiếp cận này, toàn bộ tập kết quả không phân trang phải vừa với 16MB.
btown,

7
Đây là vàng nguyên chất! Tôi đã cố gắng làm cho việc này thành công.
Henrique Miranda

4
Cảm ơn chàng trai! Tôi cần { $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}(điền vào sau {$group:{}}để đếm tổng số tìm thấy.
Liberateur

1
Làm thế nào để bạn áp dụng giới hạn cho các kết quả đặt ra? Kết quả bây giờ là một mảng lồng nhau
valen

@valen Bạn có thể thấy dòng mã cuối cùng "'results' => array ('$ slice' => array ('$ results', $ via, $ length))" Tại đây bạn có thể áp dụng giới hạn và bỏ qua các thông số
Anurag pareek

83

Kể từ v.3.4 (tôi nghĩ) MongoDB hiện đã có một nhà điều hành đường ống tổng hợp mới có tên là ' facet ' theo cách nói của riêng họ:

Xử lý nhiều đường ống tổng hợp trong một giai đoạn trên cùng một bộ tài liệu đầu vào. Mỗi đường ống con có trường riêng của nó trong tài liệu đầu ra nơi kết quả của nó được lưu trữ dưới dạng một mảng tài liệu.

Trong trường hợp cụ thể này, điều này có nghĩa là người ta có thể làm điều gì đó như sau:

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

Kết quả sẽ là (với tổng số 100 kết quả):

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

13
Đây hoạt động tuyệt vời, tính đến 3.4 này sẽ là câu trả lời được chấp nhận
Adam Reis

Để chuyển đổi kết quả nhiều mảng như vậy thành đối tượng hai trường đơn giản, tôi cần một đối tượng khác $project?
SerG

1
đây phải là câu trả lời được chấp nhận. hoạt động như một sự quyến rũ.
Arootin Aghazaryan

9
Đây sẽ là câu trả lời được chấp nhận ngày hôm nay. Tuy nhiên, tôi đã tìm thấy các vấn đề về hiệu suất khi sử dụng phân trang với $ facet. Câu trả lời được bình chọn khác cũng có vấn đề về hiệu suất với $ slice. Tôi thấy tốt hơn nếu $ bỏ qua và $ giới hạn trong đường dẫn và thực hiện một cuộc gọi riêng để đếm. Tôi đã thử nghiệm điều này với các tập dữ liệu khá lớn.
Jpepper

59

Sử dụng điều này để tìm tổng số trong bộ sưu tập kết quả.

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

3
Cảm ơn. Tuy nhiên, tôi đã sử dụng "lượt xem" trong mã hóa của mình để lấy số lượng nhóm tương ứng (tức là, nhóm 1 => 2 bản ghi, nhóm 3 => 5 bản ghi, v.v.). Tôi muốn tính số bản ghi (tức là tổng số: 120 bản ghi). Hy vọng bạn hiểu ..
user2987836

34

Bạn có thể sử dụng hàm toArray và sau đó lấy độ dài của nó cho tổng số bản ghi.

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

1
Mặc dù điều này có thể không hoạt động như một giải pháp "thích hợp", nhưng nó đã giúp tôi gỡ lỗi một số thứ - nó hoạt động, ngay cả khi nó không phải là giải pháp 100%.
Johann Marx

3
Đây không phải là giải pháp thực sự.
Furkan Başaran

1
TypeError: Parent.aggregate(...).toArray is not a functionđây là lỗi tôi đã đưa ra với giải pháp này.
Mohammad Hossein Shojaeinia

Cảm ơn. Đây là những gì tôi đang tìm kiếm.
skvp

Thao tác này sẽ tìm nạp tất cả dữ liệu tổng hợp sau đó trả về độ dài của mảng đó. không phải là một thực hành tốt. thay vào đó, bạn có thể thêm {$ count: 'count'} vào đường dẫn tổng hợp
Aslam Shaik

19

Sử dụng giai đoạn đường ống tổng hợp $ count để nhận tổng số tài liệu:

Truy vấn :

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

Kết quả:

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

Điều này hoạt động giống như một sự quyến rũ, nhưng hiệu suất khôn ngoan là nó tốt?
ana.arede

Dung dịch sạch. Cảm ơn
skvp

13

Tôi đã làm theo cách này:

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

Tổng hợp sẽ trả về mảng vì vậy chỉ cần lặp lại nó và lấy chỉ mục cuối cùng.

Và cách khác để làm điều đó là:

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 bạn không cần varkhai báo cũng như mapcuộc gọi. 3 dòng đầu tiên của ví dụ đầu tiên của bạn là đủ.
Madbreaks

7

Giải pháp do @Divergent cung cấp không hoạt động, nhưng theo kinh nghiệm của tôi, tốt hơn là nên có 2 truy vấn:

  1. Đầu tiên để lọc và sau đó nhóm theo ID để lấy số phần tử được lọc. Đừng lọc ở đây, nó là không cần thiết.
  2. Truy vấn thứ hai lọc, sắp xếp và phân trang.

Giải pháp với việc đẩy $$ ROOT và sử dụng $ slice sẽ gặp phải giới hạn bộ nhớ tài liệu là 16MB cho các bộ sưu tập lớn. Ngoài ra, đối với các bộ sưu tập lớn, hai truy vấn cùng nhau dường như chạy nhanh hơn truy vấn có $$ ROOT đẩy. Bạn cũng có thể chạy chúng song song, vì vậy bạn chỉ bị giới hạn bởi tốc độ chậm hơn của hai truy vấn (có thể là truy vấn sắp xếp).

Tôi đã giải quyết với giải pháp này bằng cách sử dụng 2 truy vấn và khung tổng hợp (lưu ý - tôi sử dụng node.js trong ví dụ này, nhưng ý tưởng giống nhau):

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
Thông thường, một phương pháp hay là bao gồm văn bản giải thích cùng với câu trả lời mã.

3

Điều này có thể hoạt động cho nhiều điều kiện đối sánh

            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

Tôi cần tổng số tuyệt đối sau khi áp dụng tổng hợp. Điều này đã làm việc cho tôi:

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

Kết quả:

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

2

Dưới đây là một số cách để nhận tổng số bản ghi khi thực hiện Tổng hợp MongoDB:


  • Sử dụng $count:

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

    Để nhận được 1000 bản ghi, điều này mất trung bình 2 ms và là cách nhanh nhất.


  • Sử dụng .toArray():

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

    Để nhận được 1000 bản ghi, điều này mất trung bình 18 ms.


  • Sử dụng .itcount():

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

    Để nhận được 1000 bản ghi, điều này mất trung bình 14 ms.



0

Nếu bạn không muốn nhóm, hãy sử dụng phương pháp sau:

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


Tôi nghĩ người đặt câu hỏi muốn phân nhóm, dựa trên chủ đề.
mjaggard
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.