MongoDB Aggregation : 총 레코드 수를 얻는 방법은 무엇입니까?


102

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 2013

$ facet 살펴보기 이것은 도움이 될 수 있습니다. stackoverflow.com/questions/61812361/…
Soham

답변:


100

이것은 페이지가 매겨진 결과와 단일 쿼리에서 동시에 총 결과 수를 얻기 위해 가장 일반적으로 묻는 질문 중 하나입니다. 마침내 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

7
이것은 순금입니다! 나는이 일을하기 위해 지옥을 통과하고 있었다.
Henrique Miranda 2011

4
고마워! 나는 juste 필요 { $group: { _id: null, count: { $sum:1 }, result: { $push: '$$ROOT' }}}후 (삽입 {$group:{}}카운트 총을 발견합니다.
Liberateur

1
결과 세트에 제한을 어떻게 적용합니까? 결과는 이제 중첩 된 배열입니다
valen

@valen 코드의 마지막 줄 " 'results'=> array ( '$ slice'=> array ( '$ results', $ skip, $ length))"여기에서 제한을 적용하고 매개 변수를 건너 뛸 수 있습니다
Anurag pareek

83

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

13
이것은이 허용 대답해야 3.4로, 좋은 작품
아담리스

이렇게 배열 된 결과를 간단한 두 필드 개체로 변환하려면 다른 것이 필요 $project합니까?
SerG

1
이것은 이제 받아 들여진 대답이어야합니다. 매력처럼 작동했습니다.
Arootin Aghazaryan

9
이것은 오늘날 받아 들여지는 대답이어야합니다. 그러나 $ facet과 함께 페이징을 사용할 때 성능 문제를 발견했습니다. 다른 투표 된 답변은 $ slice의 성능 문제도 있습니다. 파이프 라인에서 $ skip 및 $ limit를 사용하고 별도의 count 호출을하는 것이 더 낫다는 것을 알았습니다. 나는 이것을 상당히 큰 데이터 세트에 대해 테스트했습니다.
Jpepper 19

59

이를 사용하여 결과 수집에서 총 개수를 찾습니다.

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

3
감사. 그러나 나는 해당 그룹 수 (즉, 그룹 1 => 2 개 레코드, 그룹 3 => 5 개 레코드 등)를 얻기 위해 코딩에서 "뷰"를 사용했습니다. 레코드 수를 얻고 싶습니다 (즉, 총 120 개 레코드). 당신은 .. 이해 희망
user2987836에게

34

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

이렇게하면 집계 된 모든 데이터를 가져온 다음 해당 배열의 길이를 반환합니다. 좋은 습관이 아닙니다. 대신 집계 파이프 라인에 {$ count : 'count'}를 추가 할 수 있습니다
Aslam Shaik

19

사용 $ 카운트 집계 파이프 라인 단계를 총 페이지 수를 얻을 수 :

쿼리 :

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에서 제공하는 솔루션은 작동하지만 내 경험상 두 가지 쿼리를 갖는 것이 좋습니다.

  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
일반적으로 코드 답변과 함께 설명 텍스트를 포함하는 것이 좋습니다.

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
}

2

다음은 MongoDB 집계를 수행하는 동안 총 레코드 수를 얻는 몇 가지 방법입니다.


  • 사용 $count:

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

    1000 개의 레코드를 얻으려면 평균 2ms가 걸리며 가장 빠른 방법입니다.


  • 사용 .toArray():

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

    1000 개의 레코드를 얻으려면 평균 18ms가 걸립니다.


  • 사용 .itcount():

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

    1000 개의 레코드를 얻으려면 평균 14ms가 걸립니다.



0

그룹화하지 않으려면 다음 방법을 사용하십시오.

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


질문을하는 사람이 주제에 따라 그룹화하고 싶어한다고 생각합니다.
mjaggard
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.