iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
JavaScript

不要再說你不會後端!30 天 Node.js 降維打擊指南 系列 第 20 篇

# Day 20:MongoDB 聚合管道 (Aggregation Pipeline) 的數據煉金術

  • 分享至 

  • xImage
  •  

在 Day 19 中,我們學會了如何用 MongoDB 儲存無綱要的 JSON 文件,這對於存取靈活的玩家狀態或多變的感測器日誌非常方便。但如果老闆或企劃今天要求你:「幫我統整出這個月所有玩家在 Roguelike 模式下的平均通關時間與總傷害量排名。」你該怎麼做?

難道要把幾十萬筆資料全部 find() 撈回 Node.js 的記憶體裡,再用 for 迴圈慢慢加總嗎?絕對不行,你的伺服器會當場爆炸。

這時候,我們就需要 MongoDB 最強大的報表神器:聚合管道 (Aggregation Pipeline)。


1. 什麼是聚合管道?

想像一條「工廠輸送帶」。
原始資料 (Raw Data) 從入口送進來,接著會經過一連串的「加工關卡 (Stages)」。
第一關先把不要的瑕疵品挑掉,第二關把剩下的商品分類裝箱,第三關貼上重量標籤,第四關按照重量排序... 最後出來的,就是一份完美的統計報表。

這完全等同於 SQL 裡的 WHERE + GROUP BY + SUM() + ORDER BY 組合技。


2. 核心加工關卡 (Stages)

在 MongoDB 裡,最常用的關卡有這五個:

  1. $match (篩選):類似 SQL 的 WHERE。用來過濾資料,通常放在輸送帶的第一關,提早剃除不要的資料以節省效能。
  2. $group (分組):類似 SQL 的 GROUP BY。把某個欄位值相同的資料分在同一組,並支援各種數學計算 (如總和 $sum、平均 $avg、最大值 $max)。
  3. $sort (排序):類似 SQL 的 ORDER BY。將處理好的資料由大到小 (-1) 或由小到大 (1) 排序。
  4. $project (投影/塑型):類似 SQL 的 SELECT。決定最後輸出的報表只要顯示哪些欄位,甚至能把欄位重新命名。
  5. $limit (限制筆數):只取前 N 筆資料(常與 $sort 搭配用來做排行榜)。

3. 實戰演練:Roguelike 遊戲戰鬥結算排行

假設我們資料庫裡有一個 battle_logs (戰鬥紀錄) 的 Collection,裡面存著每次玩家挑戰關卡的結果。

原始資料長這樣:

[
  { "playerName": "Bibo", "weapon": "大劍", "damage": 5000, "isCleared": true },
  { "playerName": "Alice", "weapon": "弓箭", "damage": 3200, "isCleared": true },
  { "playerName": "Bibo", "weapon": "大劍", "damage": 4500, "isCleared": false },
  { "playerName": "Charlie", "weapon": "魔法書", "damage": 8000, "isCleared": true }
]

任務目標:
「找出所有通關成功 (isCleared: true) 的紀錄,並依照使用的武器 (weapon) 進行分組,計算出每種武器的總輸出傷害與使用次數,最後依總傷害由高到低排序。」

在 Node.js 中使用 Mongoose 實作:

import mongoose from 'mongoose';

// 假設 BattleLog Model 已經建立好
// const BattleLog = mongoose.model('BattleLog', schema);

const getWeaponDamageRanking = async () => {
    try {
        // 使用 .aggregate() 並傳入一個陣列 (代表輸送帶的各個關卡順序)
        const report = await BattleLog.aggregate([
            // 第 1 關:$match - 只挑選通關成功的紀錄
            { 
                $match: { isCleared: true } 
            },
            
            // 第 2 關:$group - 依照武器種類分組,並計算總和
            {
                $group: {
                    _id: "$weapon",               // 分組的依據 (注意欄位名稱前面要加 $)
                    totalDamage: { $sum: "$damage" }, // 將每筆資料的 damage 累加
                    usageCount: { $sum: 1 }       // 每出現一筆就 +1 (計算次數)
                }
            },
            
            // 第 3 關:$sort - 依照剛算出來的 totalDamage 由高到低 (-1) 排序
            { 
                $sort: { totalDamage: -1 } 
            },
            
            // 第 4 關:$project - 美化輸出的欄位名稱
            {
                $project: {
                    _id: 0,                       // 隱藏預設的 _id
                    weaponName: "$_id",           // 把原本分組的 _id 換個好懂的名字
                    totalDamage: 1,               // 顯示這個欄位
                    usageCount: 1                 // 顯示這個欄位
                }
            }
        ]);

        console.log(" 武器傷害排行榜:", report);
        // 預期輸出結果:
        // [
        //   { weaponName: "魔法書", totalDamage: 8000, usageCount: 1 },
        //   { weaponName: "大劍", totalDamage: 5000, usageCount: 1 },
        //   { weaponName: "弓箭", totalDamage: 3200, usageCount: 1 }
        // ]

    } catch (error) {
        console.error("生成報表失敗:", error);
    }
};

聚合管道是 MongoDB 在應對大數據分析時的超級武器。當前端向你討要各種奇形怪狀的統計圖表資料時,熟練掌握 Pipeline 就能讓資料庫在底層高速幫你算好一切,Node.js 只要負責把精美的結果 JSON 丟給前端即可。


上一篇
Day 19:MongoDB 降維打擊 — 無綱要 (Schema-less) 的極速開發
系列文
不要再說你不會後端!30 天 Node.js 降維打擊指南 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言