04-30 12:17
Notice
Recent Posts
Recent Comments
관리 메뉴

Scientific Computing & Data Science

[MongoDB] Aggregation / The Basic 본문

Data Science/MongoDB

[MongoDB] Aggregation / The Basic

cinema4dr12 2014. 2. 16. 12:06

by Geol Choi | 

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_dt"가 "01/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_dt"가 "01/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] : "total"과 "count" 값을 초기화한다.

[Line 10 - 16] : "result"를 함수의 인자로 취하고, "weekdays"라는 array형 변수에 Sunday(0)에서 Saturday(6)까지의 값을 할당하고, "result.day_of_week"에 요일값을 계산하여 저장하고, "result.total"을 "result.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는 사용자가 필요로 하는 최소의 정보(어떤 연산을 거쳐 얻은 결과값)만을 전달하여 데이터의 양을 최소화하는데 사용되며, 매우 중요하고 요긴하게 사용된다.

* 주의사항


Comments