본문 바로가기
NoSQL

MongoDB - Aggregation

by Mi.Ro 2023. 8. 9.

Aggregation

  • 하나 이상의 collection에 대해 분석 가능
  • stage별로 분석되는 pipeline의 개념
  • 각 stage는 tuning 가능한 process unit
  • 모든 stage에서 data input과 output은 document 형태임
  • 마지막 stage에서 생성된 document에 대한 cursor가 return됨

Aggregation pipeline

Pipeline stages : $match, $project, $count, $limt, $sort, $skip, $geoNear, $lookup ...

  • $match : 조건 검색 

     > db.c.find({ A : 'B' })

     > db.c.aggregate([{$match:{A:'B'}}])  

  • $count : 개수 세기

     > db.c.find({ A : 'B' }).count()

     > db.c.count({ A : 'B' })

     > db.c.aggregate([{$match : {A : 'B'}}, {$count : 'count_B'}])  

  • $project : 필요한 field만 projection

     > db.c.find({ A : 'B' }, {_id:0, A:1})

     > db.c.aggregate([{$match:{A:'B'}}, {$project : {_id:0, A:1}}])  

  • $limt : return되는 문서 개수 제한

     > db.c.find({ A : 'B' }).limit(5)

     > db.c.aggregate([{$match:{A:'B'}}, {$limit : 5}])  

  • $sort : 주어진 field에 대해 정렬

     > db.c.find({ A : 'B' }).sort({A:1})

     > db.c.aggregate([{$match:{A:'B'}}, {$sort : {A :1}}])  

  • $skip : 주어진 개수만큼 skip하고 return

     > db.c.find({ A : 'B' }).skip(2)

     > db.c.aggregate([{$match:{A:'B'}}, {$skip : 2}])  

  • $unwind : array field의 각 element를 하나씩 return

     > db.c.aggregate([{$match:{A:'B'}}, {$unwind : '$name'}])  

        -> name 내부 array의 각 element마다 결과가 출력

  • stage의 순서 확인하기

     > db.c.find({ A : 'B' }).sort({A:1}).limit(5), db.c.find({ A : 'B' }).limit(5).sort({A:1})

       -> 두개 결과 같음. 쓴 순서와 상관없이 MongoDB 내부적으로 sort 후 limit를 함

     > db.c.aggregate([{$match:{A:'B'}}, {$sort : {A :1}}, {$limit : 5}])   -> sort 후 limt를 함

     > db.c.aggregate([{$match:{A:'B'}}, {$limit : 5}, {$sort : {A :1}}])  ->  limit 후 sort를 함

Projection으로 New fields 만들기

  • $project에 기존 field로 new field name 정의할 수 있음('$기존필드' 사용)

     > db.c.find({ A : 'B' }, {_id:0, A:1, 'B.d':1})  -> projection

     > db.c.aggregate([{$match:{A:'B'}}, {$project : {_id:0, A:1, B_name : '$B.d'}}])

  • $를 사용 안하면 ' ' 안의 이름 자체가 새로운 field에 대한 value로 들어감
  • 해당 field에 내용이 없으면 결과가 안나옴

Project내부 operator

Pipeline operator : $arrayElemAt , $size , $slice , $filter , $cond, $switch, $avg, $push, $switch, $min, $max 

  • $arrayElemAt : array element 중 주어진 index에 해당하는 element값을 새로운 field 이름의 value로 반환

     > db.c.aggregate([{$project:{ new_B : { $arrayElemAt : ['$B', 0]} }} ])

       -> B field의 0번째 index의 값을 new_B라는 새로운 field의 value로 return

  • $size : array의 총 element 개수를 return

     > db.c.aggregate([{$project:{ num_B : { $size : '$B'} }} ])

       -> B field의 크기(element 수)를 num_B로 return

  • $slice : array 내 선택한 index의 element만 return

     > db.c.aggregate([{$project:{ new_B : { $slice : '$B', 0, 2} }} ])

       -> B field에서 0, 2번째 index에 해당하는 element만 new_B로 return

  • $filter : 주어진 조건에 해당하는 subset만 return함. as로 내부변수 정의, $$this로 as 대체 가능

     > db.c.aggregate([{$project:{ new_B : { $filter : {input : '$B.c', as : 'B_c', cond : {$gt : '$$B_c', 10}} }}} ])

       -> B.c를 내부변수 B_c로 rename하고 B_c가 10보다 큰 것만 new_B로 filter하여 return

     > db.c.aggregate([{$project:{ new_B : { $filter : {input : '$B.c', cond : {$gt : '$$this', 10}} }}} ])

       -> B.c를 B_c로 rename하고 B_c가 10보다 큰 것만 new_B로 filter하여 return

  • $cond  : 주어진 조건에 맞거나 맞지 않을 때 지정한 값 return

     > db.c.aggregate([{$project : {A : {$cond : {if: {$gt : ['$a', '$b']}, then: 'good' else: 'bad'}}}} ])

       -> cond 내부 if 조건에 맞으면 then 뒤('good'), 아니면 else 뒤 ('bad') return

     > db.c.aggregate([{$project : {A : {$cond : [{$gt : ['$a', '$b']}, 'good', 'bad'] }}} ])

       -> cond 내부 array의 조건에 맞으면 조건 뒤 첫번째 값 return, 아니면 두번째 값 return

  • $switch : case expression이 true값을 찾으면 flow를 breakout하고 빠져나옴

     > db.c.aggregate([{$project : A: {$switch : {branches : [

                                    {case : {$eq : ['$B', 10]}, then : 1},

                                    {case : {$eq : ['$B', 20]}, then : 2}],

                                      default : 0}} }} ])

       -> case가 조건에 맞으면 빠져나옴. 맞는 조건이 없으면 default 값 return

  • $max, $min, $avg, $sum : array에서 해당 연산 가능

     > db.c.aggregate([{$project:{ max_B : { $max : '$B'}, min_B : {$min : '$B'} }} ])

       -> B field의 max, min값을 max_B, min_B로 return

  • $ne:[]

     > db.c.aggregate([{$match : { A : {$exists:true, $ne: [] }}} ])

       -> A값이 없는 document는 return안됨

Grouping

  • $group : _id 에 해당하는 field로 groupyby함

     > db.c.aggregate([{$group: {_id : '$A', avg : {$avg : 'num' }, min : {$min : 'num'}}}, {$sort : {$avg : -1}} ])

       -> _id로 설정한 A field로 group by해서 avg, min값을 계산하고 avg값으로 내림차순으로 return

Geospatial in aggregation

  • $match stage에서 $geoWithin 사용 가능
  • $near나 $nearSphere 사용 불가

     > db.c.find([{'coord' : {$geoWithin : {$center : [[10.1, 11.1], 0.1/111.1]}}} ])

     > db.c.aggregate([{$match : {'coord' : {$geoWithin : {$center : [[10.1, 11.1], 0.1/111.1]}}} } ])

 

  • $match 대신 $geoNear 사용 가능

     > db.c.aggregate([{$geoNear: {near : {type : 'Point', coordinates : [10.1, 11.1]}, key : 'address.coord', distanceField : "DIST", maxDistance : 100} } ])

$match stage에서의 restriction

  • $match stage에서는 $<field> 사용 안됨
  • $expr을 사용해서 $<field> 사용 가능

     > db.c.aggregate([{$match : {$expr : {$gt : ["$A", "$num"]}}} ])

'NoSQL' 카테고리의 다른 글

MongoDB - text search  (0) 2023.08.08
MongoDB - Geospatial data  (0) 2023.08.08
MongoDB - Indexing  (0) 2023.08.07
MongoDB - Querying  (0) 2023.08.07
MongoDB - Crud operations  (0) 2023.08.07