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는 사용자가 필요로 하는 최소의 정보(어떤 연산을 거쳐 얻은 결과값)만을 전달하여 데이터의 양을 최소화하는데 사용되며, 매우 중요하고 요긴하게 사용됩니다.
- 주의사항
db.collection.group()
은 sharded cluster (일종의 분산 데이터베이스 클러스터)에는 적용할 수 없습니다. 분산 데이터베이스 클러스터의 경우, 집합 프레임웍(aggregation framework) 또는 맵리듀스(map reduce)를 사용합니다.
결과 세트는 최대 BSON 도큐먼트 사이즈 이내에서만 허용됩니다.