Skip to content

MongoDB는 복잡한 데이터 분석을 위해 다양한 맵리듀스(Map Reduce) 등 다양한 집합(Aggregation) 도구를 제공합니다.


count

count 연산자는 컬렉션 내의 도큐먼트 개수를 파악할 수 있는 메써드입니다.

다음과 같이 people 컬렉션에 3개의 도큐먼트를 추가한 후,

> db.people.insert({username: "user1"});
> db.people.insert({username: "user2"});
> db.people.insert({username: "user3"});

count() 메써드를 이용하여 개수를 구하면 다음과 같습니다:

> db.people.find().pretty()
{ "_id" : ObjectId("53003fc9f00107dcac226820"), "username" : "user1" }
{ "_id" : ObjectId("53003fcaf00107dcac226821"), "username" : "user2" }
{ "_id" : ObjectId("53003fcbf00107dcac226822"), "username" : "user3" }
> db.people.count()
3

distinct

distinct 연산자는 주어진 key 값에 대해 서로 "구별"되는 모든 값을 찾습니다. 예를 들어 다음과 같은 도큐먼트가 주어졌다고 하도록 하겠습니다.

> db.people.insert({username: "user1", age: 10});
> db.people.insert({username: "user2", age: 24});
> db.people.insert({username: "user3", age: 30});
> db.people.insert({username: "user4", age: 32});
> db.people.insert({username: "user5", age: 24});
> db.people.insert({username: "user6", age: 10});
> db.people.find().pretty()
{
  "_id" : ObjectId("5300c91533e71a1e198a9d7d"),
  "username" : "user1",
  "age" : 10
}
{
  "_id" : ObjectId("5300c91733e71a1e198a9d7e"),
  "username" : "user2",
  "age" : 24
}
{
  "_id" : ObjectId("5300c91833e71a1e198a9d7f"),
  "username" : "user3",
  "age" : 30
}
{
  "_id" : ObjectId("5300c91a33e71a1e198a9d80"),
  "username" : "user4",
  "age" : 32
}
{
  "_id" : ObjectId("5300c91c33e71a1e198a9d81"),
  "username" : "user5",
  "age" : 24
}
{
  "_id" : ObjectId("5300c91f33e71a1e198a9d82"),
  "username" : "user6",
  "age" : 10
}

다음과 같이 distinct 값은 컬렉션의 이름을, "key" 값은 구별할 대상이 되는 키 이름을 입력한다.

> db.runCommand({distinct : "people", key : "age"})
{
  "values" : [
    10,
    24,
    30,
    32
  ],
  "stats" : {
    "n" : 6,
    "nscanned" : 6,
    "nscannedObjects" : 6,
    "timems" : 0,
    "cursor" : "BasicCursor"
  },
  "ok" : 1
}

즉, people이라는 컬렉션의 age 키에 대한 값을 구별하여(중복을 제거하고) 표현합니다.

username에 대한 age[10, 24, 30, 32, 24, 10]인데 이 중 중복이 되는 [10, 24]를 한 번씩 제거하면 구별되는 age의 집합은 [10, 24, 30, 32]가 됩니다.


group

group은 보다 복잡한 집합을 수행합니다.

그룹화 할 key를 선택하면 컬렉션을 선택된 key 값에 대해 각각 그룹을 만듭니다.

각 그룹에 대해 그룹 멤버인 도큐먼트들을 모아 결과 도큐먼트를 생성할 수 있습니다.

용법은 다음과 같습니다.

db.collection.group({ key, reduce, initial, [keyf,] [cond,] finalize })

db.colletion.group()은 다음을 포함하는 하나의 도큐먼트를 허용합니다.

필드 형태 설명
key document 그룹화 할 필드(들)
reduce function 그룹 오퍼레이션을 수행하는 동안 도큐먼트들에 대해 수행할 집합 함수(aggregation function)
initial document 집합 결과 도큐먼트의 초기화
keyf function optional. key 필드에 대한 대체 필드. 그룹화 key로 사용할 "key object"를 생성하는 함수를 지정.
cond document optional. 컬렉션 내의 어떤 도큐먼트를 처리할 지를 결정하는 선택 기준. cond 필드 생략 시 컬렉션 내 모든 도큐먼트를 처리.
finalize function optional. db.collection.group()이 최종 값을 반환하기 전 결과 세트 내 각 아이템을 실행하는 함수.

다음 예로 설명하도록 하겠습니다.

orders 컬렉션의 형태는 다음과 같습니다 (아래의 형태로 여러 개의 도큐먼트를 준비했다고 가정합니다):

db.orders.insert({
  _id: ObjectId("5085a95c8fada716c89d0021"),
  ord_dt: ISODate("2012-07-01T04:00:00Z"),
  ship_dt: ISODate("2012-07-02T04:00:00Z"),
  item: { sku: "abc123",
          price: 1.99,
          uom: "pcs",
          qty: 25 }
})

상기 컬렉션을 ord_dt(ordered date; 주문 일자)와 ship_dt(shipped date; 선적 일자)의 두 개의 key로 그룹화 하되, ord_dt01/01/2012(2012년 1월 1일) 보다 큰(보다 최근의) 날짜인 것만을 그룹화 하면 다음과 같이 array 형태로 결과가 출력됩니다 (앞서 설명하였듯이 사용자가 위의 컬렉션 형태로 도큐먼트를 준비했다고 가정합니다).

[ { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "abc123"},
  { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "abc456"},
  { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "bcd123"},
  { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "efg456"},
  { "ord_dt" : ISODate("2012-06-01T04:00:00Z"), "item.sku" : "abc123"},
  { "ord_dt" : ISODate("2012-06-01T04:00:00Z"), "item.sku" : "efg456"},
  { "ord_dt" : ISODate("2012-06-01T04:00:00Z"), "item.sku" : "ijk123"},
  { "ord_dt" : ISODate("2012-05-01T04:00:00Z"), "item.sku" : "abc123"},
  { "ord_dt" : ISODate("2012-05-01T04:00:00Z"), "item.sku" : "abc456"},
  { "ord_dt" : ISODate("2012-06-08T04:00:00Z"), "item.sku" : "abc123"},
  { "ord_dt" : ISODate("2012-06-08T04:00:00Z"), "item.sku" : "abc456"} ]

이번에는 reduce 필드를 이용하여, 상기와 동일한 오퍼레이션을 수행하되, 각 그룹에 대해 합을 계산해 보겠습니다:

db.orders.group( {
   key: { ord_dt: 1, 'item.sku': 1 },
   cond: { ord_dt: { $gt: new Date( '01/01/2012' ) } },
   reduce: function ( curr, result ) {
               result.total += curr.item.qty;
           },
   initial: { total : 0 }
} )

역시 다음과 같이 array 형태로 결과가 출력됩니다:

[ { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "abc123", "total" : 25 },
  { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "abc456", "total" : 25 },
  { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "bcd123", "total" : 10 },
  { "ord_dt" : ISODate("2012-07-01T04:00:00Z"), "item.sku" : "efg456", "total" : 10 },
  { "ord_dt" : ISODate("2012-06-01T04:00:00Z"), "item.sku" : "abc123", "total" : 25 },
  { "ord_dt" : ISODate("2012-06-01T04:00:00Z"), "item.sku" : "efg456", "total" : 15 },
  { "ord_dt" : ISODate("2012-06-01T04:00:00Z"), "item.sku" : "ijk123", "total" : 20 },
  { "ord_dt" : ISODate("2012-05-01T04:00:00Z"), "item.sku" : "abc123", "total" : 45 },
  { "ord_dt" : ISODate("2012-05-01T04:00:00Z"), "item.sku" : "abc456", "total" : 25 },
  { "ord_dt" : ISODate("2012-06-08T04:00:00Z"), "item.sku" : "abc123", "total" : 25 },
  { "ord_dt" : ISODate("2012-06-08T04:00:00Z"), "item.sku" : "abc456", "total" : 25 } ]

다음 예는 ord_dt01/01/2012 보다 큰(보다 최근의) 날짜인 것들 중 keyf 필드를 이용하여 이 중 day_of_week로 그룹화 하여 각 그룹에 대해 qty 값을 합산하고 개수를 파악하여 평균값을 구하는 것입니다:

db.orders.group( {
   keyf: function(doc) {
             return { day_of_week: doc.ord_dt.getDay() } ; },
   cond: { ord_dt: { $gt: new Date( '01/01/2012' ) } },
   reduce: function ( curr, result ) {
              result.total += curr.item.qty;
              result.count++;
           },
   initial: { total : 0, count: 0 },
   finalize: function(result) {
               var weekdays = [ "Sunday", "Monday", "Tuesday",
                                "Wednesday", "Thursday",
                                "Friday", "Saturday" ];

               result.day_of_week = weekdays[result.day_of_week];
               result.avg = Math.round(result.total / result.count);

   }
} )

[Line 2 - 3]

day_of_week이라는 변수에 도큐먼트의 ISODate로 되어있는 ord_dt의 요일을 구합니다.

이는 getDay() 함수를 이용하여 구할 수 있습니다. 예를 들어,

> var myOrder = db.orders.findOne({})
> myOrder
{
  "_id" : ObjectId("5085a95c8fada716c89d0021"),
  "ord_dt" : ISODate("2012-07-01T04:00:00Z"),
  "ship_dt" : ISODate("2012-07-02T04:00:00Z"),
  "item" : {
    "sku" : "abc123",
    "price" : 1.99,
    "uom" : "pcs",
    "qty" : 25
  }
}
> myOrder.ord_dt.getDay()
0

숫자 "0"은 일요일, "1"은 월요일, "2"는 화요일, "3"은 수요일, "4"는 목요일, "5"는 금요일, "6"은 토요일을 의미합니다.

결과값으로 "0"을 반환하였는데, 달력을 찾아보면 2012-07-01(2012년 7월 1일)이 일요일임을 확인할 수 있을 것입니다.

즉, keyf의 역할은 각 도큐먼트의 "ord_dt"를 요일로 변환하는 것입니다.

[Line 4]

ord_dt가 2012년 1월 1일 보다 최근의 날짜를 걸러내는 것입니다.

참고로 Date() 함수는 주어진 날짜를 ISODate로 변환합니다. 예를 들어 2013년 2월 18일에 대해

> var today = new Date('02/18/2014')
> today
ISODate("2014-02-17T15:00:00Z")

과 같이 ISO표준시로 변환합니다. ISO 표준시에 대해 자세한 정보는 여기를 클릭하여 찾아보시기 바랍니다.

[Line 5 - 8]

reduce 필드의 함수는 두 가지 인자를 취하는데, 첫번째 인자는 "현재 도큐먼트"를 두번째 인자는 "연산 결과의 도큐먼트"입니다.

result.total += curr.item.qty는 현재 도큐먼트의 item array에서 qty 값을 결과(result)의 total 값으로 누적 합산합니다.

result.count++은 처리된 도큐먼트의 총 개수를 계산하기 위한 것입니다.

[Line 9]

totalcount 값을 초기화합니다.

[Line 10 - 16]

result를 함수의 인자로 취하고, weekdays라는 array형 변수에 Sunday(0)에서 Saturday(6)까지의 값을 할당하고, result.day_of_week에 요일값을 계산하여 저장하고, result.totalresult.count로 나누고 반올림하여 평균값을 구한 후 result.avg에 저장합니다.

다음은 이에 대한 연산의 한 예입니다:

[ { "day_of_week" : "Sunday", "total" : 70, "count" : 4, "avg" : 18 },
  { "day_of_week" : "Friday", "total" : 110, "count" : 6, "avg" : 18 },
  { "day_of_week" : "Tuesday", "total" : 70, "count" : 3, "avg" : 23 } ]

특히 finalize 필드를 finalizer라고도 하는데, finalizer는 사용자가 필요로 하는 최소의 정보(어떤 연산을 거쳐 얻은 결과값)만을 전달하여 데이터의 양을 최소화하는데 사용되며, 매우 중요하고 요긴하게 사용됩니다.

  • 주의사항

db.collection.group()sharded cluster (일종의 분산 데이터베이스 클러스터)에는 적용할 수 없습니다. 분산 데이터베이스 클러스터의 경우, 집합 프레임웍(aggregation framework) 또는 맵리듀스(map reduce)를 사용합니다.

결과 세트는 최대 BSON 도큐먼트 사이즈 이내에서만 허용됩니다.