본문으로 바로가기

👨‍🏫 고급(?) stage

 

$bucket

https://docs.mongodb.com/manual/reference/operator/aggregation/bucket/

 

흔히 말하는 '버케팅'을 합니다. 버케팅하려는 값이 정수면 $group을 사용하고, 실수면 $bucket이 좋습니다.

왜냐하면 $bucket은 값을 제각각 나누는 것이 아니라 범위로 나누거든요.

 

{
  $bucket: {
      groupBy: <expression>, // 버케팅할 기준
      boundaries: [ <lowerbound1>, <lowerbound2>, ... ], // 버케팅 기준값의 구간
      default: <literal>, // optional. 구간 외의 도큐먼트를 처리할 필드명
      output: {
         <output1>: { <$accumulator expression> }, // optional. 버케팅한 후 출력 결과를 표시할 방법
         ...
         <outputN>: { <$accumulator expression> }
      }
   }
}

 

rating 기준으로 묶고, 구간은 [2, 3), [3, 5) 가 됩니다.

* 개구간, 폐구간, 반개구간은 아시죠? (a,b)={xRa<x<b}, [a,b]={xRaxb}

 

이 구간 내에 속하지 않는 도큐먼트는 _id가 Others가 되고요.

최종적으로 출력할 모습은 {"count": ..., "user_ids": [...]} 모양이 되겠네요.

db.rating.aggregate([
    {$bucket: {
        groupBy: "$rating",
        boundaries: [2, 3, 5],
        default: "Others",
        output: {
           count: {$sum: 1},
           user_ids:  {$push: "$user_id"}
        }
     }}
])

출력된 결과물은 다음과 같습니다.

{"_id" : 2.0, "count" : 1.0, "user_ids" : [3.0]}
{"_id" : 3.0, "count" : 4.0, "user_ids" : [4.0, 1.0, 7.0, 8.0]}
{"_id" : "Others", "count" : 5.0, "user_ids" : [2.0, 6.0, 9.0, 10.0, 5.0]}

 

 

$bucketAuto

https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/

 

$bucket이 특정 구간을 수작업으로 지정했다면, $bucketAuto는 범위를 자동으로 설정해준다. 

 

{
  $bucketAuto: {
      groupBy: <expression>, // 버케팅할 기준
      buckets: <number>, // 몇 개의 그룹으로 나눌 것인가?
      output: {
         <output1>: { <$accumulator expression> }, // optional
         ...
      }
      granularity: <string> // 어떤 수열로 버케팅 기준을 분류할 것인가
  }
}

 

granularity에 올 수 있는 값은 다음과 같다.

 

R로 시작하는 것은 레나드 수열

E로 시작하는 것은 E수열

1-2-5는 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50...로 이어지는 수열

POWERSOF2는 공비(r)가 2인 등비수열

 

  • "R5"
  • "R10"
  • "R20"
  • "R40"
  • "R80" 
  • "1-2-5"
  • "E6"
  • "E12"
  • "E24"
  • "E48"
  • "E96"
  • "E192"
  • "POWERSOF2"

 

 

$addFields

https://docs.mongodb.com/manual/reference/operator/aggregation/addFields/

 

새로운 필드를 생성할 수 있다. 만약, 생성하고자 하는 필드가 존재하면 덮어쓰고, 점 연산자를 통해 임베디드 도큐먼트에도 필드를 추가할 수 있다.

{ $addFields: { <newField>: <expression>, ... } }
// nice라는 필드를 새로 만들고 boat라는 문자열을 할당합니다.
db.rating.aggregate([
    {$addFields:  {nice: "boat"}},
    {$limit: 5}
])

 

$facet

https://docs.mongodb.com/manual/reference/operator/aggregation/facet/

 

facet을 번역하면 양상(그러니까 그냥 일이나 상황이 보여지는 방식)이다.

도큐먼트를 집계해서 정해진 필드에 연산 결과를 배열 형식으로 값을 출력한다.

쉽게 말해, 여러가지 stage를 거친 값들을 출력한다는 것이다.

{ $facet:
   {
      <outputField1>: [ <stage1>, <stage2>, ... ],
      <outputField2>: [ <stage1>, <stage2>, ... ],
      ...

   }
}

 

예시를 보면, byRating이란 필드 명으로 출력할 stage들이 보이고, byId란 필드 명으로 출력할 stage들이 보인다.

$facet은 여러 정보를 동시에 보고 싶을 때 사용하면 유용하다.

db.rating.aggregate([
    {$facet: {
            byRating: [
                {$group: {_id: "$rating", count: {$sum: 1}}}
            ],
            byId: [
                {$bucketAuto: {groupBy: "$_id", buckets: 5}}
           ]
     }},
])
{
    "byRating" : [ 
        {
            "_id" : 5.0,
            "count" : 4.0
        }, 
        {
            "_id" : 1.0,
            "count" : 1.0
        }, 
		... 중략
    ],
    "byId" : [ 
        {
            "_id" : {
                "min" : 1.0,
                "max" : 3.0
            },
            "count" : 2
        }, 
        {
            "_id" : {
                "min" : 3.0,
                "max" : 5.0
            },
            "count" : 2
        }, 
        ...중략
    ]
}

 

 

$lookup

https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/

 

서로 다른 컬렉션의 정보를 합칠 수 있는 스테이지다. 관계형 데이터베이스의 JOIN 개념과 비슷하다.

shell에서 $lookup을 사용할 때의 전제 조건은, 두 컬렉션이 같은 DB에 있어야 하며, 샤딩되어 있지 않아야 한다.

 

mogoose와 같은 ODM은 populate라는 메서드로 $lookup을 대체하고 있으니 ODM을 사용할 때는 populate를 사용하면 된다.

 

$lookup은 다른 두 가지 형식이 있는데 3.6 버전 이상이면 둘 다 쓸 수 있다. 상황에 적절한 것을 이용하면 된다.

 

New in version 3.2.

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

 

아래와 같이 작성하면 by_month의 area_id와 area의 _id를 연결합니다. 당연히 이 두 _id는 같은 값이겠죠?

// by_month 컬렉션
db.by_month.aggregate(
[
    {$lookup: {
        from: "area", // by_month 컬렉션에서 area 컬렉션을 조인합니다. 
        localField: "area_id", // by_month 컬렉션에서 연결할 필드명
        foreignField: "_id", // area 컬렉션에서 연결할 필드명
        as: "area_data" // 결과 출력시 보여줄 필드 이름
     }},
    {$limit: 1}
]
)

 

결과

{
    "_id" : ObjectId("5c8909190da47a8507755fd8"),
    "city_or_province" : "서울",
    "county" : "종로구",
    "area_id" : ObjectId("5c88f9f70da47a8507752775"),
    .. 중략
    
    // as에서 설정한 필드 명으로 불러온 area 필드
    "area_data" : [ 
        {
            "_id" : ObjectId("5c88f9f70da47a8507752775"),
            "city_or_province" : "서울",
            "county" : "종로구",
            "population" : 152737
        }
    ]
}

 

 

New in version 3.6.

3.2 버전보다 조금 더 강력한 $lookup입니다.

{
   $lookup:
     {
       from: <collection to join>,
       let: { <var_1>: <expression>, …, <var_n>: <expression> }, // optional.
       pipeline: [ <pipeline to execute on the collection to join> ],
       as: <output array field>
     }
}

 

 

$replaceRoot

 

$sample

 

$sortByCount


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체