iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 20
0
AI & Data

從入門到精通 MongoDB系列 第 26

Day26: 聚合(Aggregation)操作(2) - $project 及 $bucket

在上一篇文章「」中我們介紹了聚合管線的操作,在 MongoDB 官方文件中我們可以看到有許多運算子支持對管線中各個階段的操作,今天這篇文章先來介紹其中兩個:$project$bucket


$project

我們先來查看 orders 這個 collection 的資料: db.orders.find()

之前有學過可以使用 projection 的概念來回傳指定的欄位:

  • 過濾 status 為 finished 的資料:db.orders.find({status: "finished"})

  • 過濾 status 為 finished 的資料,且只回傳 name 欄位:db.orders.find({status: "finished"}, {name: 1})

  • 過濾 status 為 finished 的資料,且只回傳 name 及 status 欄位:db.orders.find({status: "finished"}, {name: 1, status: 1})

  • 過濾 status 為 finished 的資料,且只回傳 name 及 status 欄位,而不回傳 _id:db.orders.find({status: "finished"}, {name: 1, status: 1, _id: 0})

使用 $project 來達到 projection 效果

我們也可以使用 $project 來達成上方指令一樣的結果:

db.orders.aggregate(
    [
        {$match: {status: "finished"}},
        {$project: {name: 1, status: 1, _id: 0}}
    ]
)

也可以產生新的欄位 "new":

db.orders.aggregate(
    [
        {
            $match: { status: "finished" } },
        {
            $project:
                {
                    name: 1,
                    status: 1,
                    _id: 0,
                    new: ["$amount", "$name"]
                }
        }
    ]
)

$toUpper

我們如果想回傳 name 欄位,並將 value 改成大寫,可以使用 $toUpper

db.orders.aggregate(
    [
        {
            $match: { status: "finished" } },
        {
            $project:
                {
                    name: {$toUpper: "$name"}
                }
        }
    ]
)

$concat, $toString

也可以新增一欄位 "new",紀錄某人花了多少錢:

db.orders.aggregate(
    [
        {
            $match: { status: "finished" } },
        {
            $project:
                {
                    name: {$toUpper: "$name"},
                    new: {
                        $concat: ["$name", " spend ", {$toString: "$amount"}]
                    }
                }
        }
    ]
)

  • 使用 $concat 來串接字串
  • $concat 只支持字串,因此 amount 要使用 $toString 改成字串型態

$cond

新增一欄位 "flag",若 amount > 200 flag 為 big,若 amount ≤ 200 flag 為 small:

db.orders.aggregate(
    [
        {
            $match: { status: "finished" } },
        {
            $project:
                {
                    name: {$toUpper: "$name"},
                    new: {
                        $concat: ["$name", " spend ", {$toString: "$amount"}]
                    },
                    flag: {
                        $cond: {
                            if: {$gt: ["$amount", 200]},
                            then: "big",
                            else: "small"
                        }
                    }
                }
        }
    ]
)

  • 使用 $cond 來執行判斷句
  • $cond 內是使用 if-then-else 來執行判斷

$project 相關 Array 的運算子

我們先新增 test 這個 collection:

db.test.insertMany(
    [
        {
            name: "Jack",
            hobby: [
                "Reading",
                "Cooking",
                "Running"
            ]
        },
        {
            name: "Mark",
            hobby: [
                "Cooking",
                "Singing",
            ]
        }
    ]
)

$size

計算每個人的嗜好有幾個,新增一欄位 "numHobby" 來記錄:

db.test.aggregate(
    [
        {
            $project:{
                _id: 0,
                name: 1,
                numHobby: {
                    $size: "$hobby"
                }
            }
        }
    ]
)

  • 使用 $size 來計算 hobby 這個 array 中有幾個元素

$slice

我們可以使用 $slice 運算子去取得 array 中的特定元素

  1. 取每個人的第一個 hobby:
    db.test.aggregate(
        [
            {
                $project:{
                    _id: 0,
                    name: 1,
                    numHobby: {
                        $size: "$hobby"
                    },
                    hobby: {
                        $slice: ["$hobby", 0, 1]
                    }
                }
            }
        ]
    )
    
    • 用 $slice 去取 hobby 這個 array 中的特定元素:從 index:0 開始取 1 個元素(第二個參數為起點,第三個參數為取幾個元素)
  2. 取每個人的前三個 hobby:
    db.test.aggregate(
        [
            {
                $project:{
                    _id: 0,
                    name: 1,
                    numHobby: {
                        $size: "$hobby"
                    },
                    hobby: {
                        $slice: ["$hobby", 0, 3]
                    }
                }
            }
        ]
    )
    
    • 取前三個 hobby,但 Mark 只有兩個 hobby,因此只有顯示兩個 hobby

對於更多聚合管線操作運算子的使用方法,可以參考以下的官方文件說明:


$bucket

我們可以使用 $bucket 運算子對資料進行分類。

先來查看 movie 這個 collection:db.movie.findOne()

使用 $bucket 進行資料分類,每個分類為一個 bucket

db.movie.aggregate(
    [
        {
            $bucket:{
                groupBy: "$year",
                boundaries: [1990, 1995, 2000, 2005, 2010, 2015, 2020],
                output: {
                    numMovies: {$sum: 1},
                    avgScore: {$avg: "$imdb_score"}
                }
            }
        }
    ]
)

  • groupby: 依照 year 這個欄位來分類
  • boundaries:分類的區間(包含前項,但不包含後項)
    • [1990,1995)
    • [1995,2000)
    • [2000,2005)
    • [2005,2010)
    • [2010,2015)
    • [2015,2020)
  • output:
    • numMovies: 對每個區間進行電影數量統計
    • avgScore:對每個區間的電影求平均的 IMDB 分數

今天介紹了兩種操作聚合管線的運算子 $project 及 $bucket,下一篇會接著介紹其他聚合管線的運算子。


上一篇
Day25: 聚合(Aggregation)操作(1) - Aggregation pipeline
系列文
從入門到精通 MongoDB26

尚未有邦友留言

立即登入留言