MongoDB 的聚合框架提供強大的資料處理能力,讓開發者能有效率地進行資料分析和轉換。文章從分組聚合開始,逐步介紹如何使用 $group$match$project 等運算子進行資料統計、篩選和投影。接著,文章示範如何運用布林運算元進行更複雜的條件判斷,並以平均年齡篩選和左外連線等實際案例,展現聚合框架的靈活性。除了聚合框架,文章也探討 MongoDB 的索引管理,說明如何建立不同型別的索引,例如升序、降序、雜湊索引等,以及如何根據需求選擇稀疏索引或部分索引,以最佳化查詢效能並提升資料函式庫整體運作效率。

MongoDB 聚合框架實戰應用

MongoDB 的聚合框架(Aggregation Framework)提供了一種強大的資料處理工具,能夠對資料進行複雜的分析和轉換。本文將探討聚合框架的各種操作和應用場景,並透過具體範例進行詳細解說。

1. 分組與聚合運算

分組是聚合框架中的基本操作,能夠根據指定的欄位將資料分組並進行各種聚合運算。

程式碼範例

db.employees.aggregate([
  {
    $group: {
      "_id": "$dept",
      "noOfEmployee": { $sum: 1 },
      "avgExp": { $avg: "$totalExp" }
    }
  }
]);

內容解密:

  • $group階段根據dept欄位進行分組。
  • "_id": "$dept"表示根據dept欄位進行分組。
  • "noOfEmployee": { $sum: 1 }計算每個部門的員工數量。
  • "avgExp": { $avg: "$totalExp" }計算每個部門員工的平均工作經驗。

2. 排序與限制結果

在聚合框架中,可以對資料進行排序和限制結果,以滿足特定的查詢需求。

程式碼範例

db.employees.aggregate([
  { $match: { dept: "Admin" } },
  { $project: { "name": 1, "dept": 1 } },
  { $sort: { name: 1 } },
  { $limit: 1 }
]);

內容解密:

  • $match階段篩選出dept為"Admin"的員工。
  • $project階段選擇要顯示的欄位。
  • $sort階段根據name欄位進行升序排序。
  • $limit階段限制結果只傳回一筆資料。

3. 布林聚合運算元

布林聚合運算元能夠在聚合框架中進行複雜的條件判斷。

程式碼範例

db.employees.aggregate([
  { $match: { dept: "Admin" } },
  {
    $project: {
      "name": 1,
      "dept": 1,
      age: { $and: [{ $gt: ["$age", 30] }, { $lt: ["$age", 36] }] }
    }
  }
]);

內容解密:

  • $and運算元用於判斷age是否大於30且小於36。
  • $gt$lt分別用於大於和小於的比較運算。

4. 平均年齡篩選範例

假設我們需要找出平均年齡大於或等於35歲,且薪水小於或等於70000的部門。

程式碼範例

db.employees.aggregate([
  { "$match": { "salary": { "$lte": 70000 } } },
  {
    "$group": {
      "_id": "$dept",
      "average_age": { "$avg": "$age" }
    }
  },
  { "$match": { "average_age": { "$gte": 35 } } }
]);

內容解密:

  • 第一階段$match篩選出薪水小於或等於70000的員工。
  • $group階段根據部門進行分組,並計算平均年齡。
  • 第二階段$match篩選出平均年齡大於或等於35的部門。

MongoDB 聚合框架應用實務

MongoDB 的聚合框架(Aggregation Framework)是一種強大的資料處理工具,能夠對資料進行複雜的處理和分析。本文將介紹 MongoDB 聚合框架的各種應用場景和實務範例。

取得特定部門的平均年齡

假設我們有一個名為 employees 的集合,包含員工的部門和年齡資訊。我們可以使用聚合框架來計算每個部門的平均年齡。

db.employees.aggregate([
  {
    $group: {
      _id: "$department",
      average_age: { $avg: "$age" }
    }
  }
])

內容解密:

  1. $group 階段用於分組資料, _id 欄位指定了分組的依據,這裡是 department 欄位。
  2. average_age 欄位使用 $avg 聚合運算元計算每個部門的平均年齡。

取得隨機樣本資料

我們可以使用 $sample 聚合階段來取得集合中的隨機樣本資料。

db.employees.aggregate([
  { $sample: { size: 1 } }
])

內容解密:

  1. $sample 階段用於取得隨機樣本資料, size 引數指定了要取得的樣本數量。

移除重複檔案

假設我們有一個名為 transactions 的集合,包含交易資料。我們可以使用聚合框架來找出重複的檔案並將其移除。

var duplicates = [];
db.transactions.aggregate([
  {
    $group: {
      _id: { cr_dr: "$cr_dr" },
      dups: { $addToSet: "$_id" },
      count: { $sum: 1 }
    }
  },
  {
    $match: {
      count: { $gt: 1 }
    }
  }
], { allowDiskUse: true })
.result
.forEach(function(doc) {
  doc.dups.shift();
  doc.dups.forEach(function(dupId) {
    duplicates.push(dupId);
  })
})
db.transactions.remove({ _id: { $in: duplicates } })

內容解密:

  1. $group 階段用於分組資料, _id 欄位指定了分組的依據,這裡是 cr_dr 欄位。
  2. dups 欄位使用 $addToSet 聚合運算元收集每個分組中的 _id
  3. $match 階段用於篩選出重複的分組, 即 count 大於 1 的分組。
  4. 將重複的 _id 收集到 duplicates 陣列中。
  5. 使用 remove 方法移除重複的檔案。

左外連線($lookup)

我們可以使用 $lookup 聚合階段來執行左外連線操作。

let col_1 = db.collection('col_1');
let col_2 = db.collection('col_2');
col_1.aggregate([
  { $match: { "_id": 1 } },
  {
    $lookup: {
      from: "col_2",
      localField: "id",
      foreignField: "id",
      as: "new_document"
    }
  }
])

內容解密:

  1. $lookup 階段用於執行左外連線操作, from 引數指定了要連線的集合。
  2. localFieldforeignField 引數指定了連線的條件。
  3. as 引數指定了連線結果的欄位名稱。

Java 和 Spring Data MongoDB 範例

以下是一個使用 Spring Data MongoDB 執行聚合操作的範例。

try {
  MongoClient mongo = new MongoClient();
  DB db = mongo.getDB("so");
  DBCollection coll = db.getCollection("employees");

  // 相當於 $match
  DBObject matchFields = new BasicDBObject();
  matchFields.put("dept", "Admin");
  DBObject match = new BasicDBObject("$match", matchFields);

  // 相當於 $project
  DBObject projectFields = new BasicDBObject();
  projectFields.put("_id", 1);
  projectFields.put("name", 1);
  projectFields.put("dept", 1);
  projectFields.put("totalExp", 1);
  projectFields.put("age", 1);
  projectFields.put("languages", 1);
  DBObject project = new BasicDBObject("$project", projectFields);

  // 相當於 $group
  DBObject groupFields = new BasicDBObject("_id", "$dept");
  groupFields.put("ageSet", new BasicDBObject("$addToSet", "$age"));
  DBObject employeeDocProjection = new BasicDBObject("$addToSet", new BasicDBObject("totalExp", "$totalExp").append("age", "$age").append("languages", "$languages").append("dept", "$dept").append("name", "$name"));
  groupFields.put("docs", employeeDocProjection);
  DBObject group = new BasicDBObject("$group", groupFields);

  // 相當於 $sort
  DBObject sort = new BasicDBObject("$sort", new BasicDBObject("age", 1));

  List<DBObject> aggregationList = new ArrayList<>();
  aggregationList.add(match);
  aggregationList.add(project);
  aggregationList.add(group);
  aggregationList.add(sort);

  AggregationOutput output = coll.aggregate(aggregationList);

  for (DBObject result : output.results()) {
    BasicDBList employeeList = (BasicDBList) result.get("docs");
    BasicDBObject employeeDoc = (BasicDBObject) employeeList.get(0);
    String name = employeeDoc.get("name").toString();
    System.out.println(name);
}
} catch (Exception ex) {
ex.printStackTrace();
}

此圖示呈現了聚合框架的流程

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title MongoDB 聚合框架與索引管理實戰

package "資料庫架構" {
    package "應用層" {
        component [連線池] as pool
        component [ORM 框架] as orm
    }

    package "資料庫引擎" {
        component [查詢解析器] as parser
        component [優化器] as optimizer
        component [執行引擎] as executor
    }

    package "儲存層" {
        database [主資料庫] as master
        database [讀取副本] as replica
        database [快取層] as cache
    }
}

pool --> orm : 管理連線
orm --> parser : SQL 查詢
parser --> optimizer : 解析樹
optimizer --> executor : 執行計畫
executor --> master : 寫入操作
executor --> replica : 讀取操作
cache --> executor : 快取命中

master --> replica : 資料同步

note right of cache
  Redis/Memcached
  減少資料庫負載
end note

@enduml

圖示內容解密:

此圖示展示了聚合框架的典型流程,包括篩選($match)、投影($project)、分組($group)和排序($sort)等階段,最終輸出結果。

MongoDB 索引管理詳解

MongoDB 的索引對於提升查詢效能至關重要,本文將探討索引的建立、刪除以及不同型別的索引。

索引建立基礎

首先,我們先建立一個名為 transactions 的集合,並插入一些測試資料:

db.transactions.insert({ cr_dr : "D", amount : 100, fee : 2});
db.transactions.insert({ cr_dr : "C", amount : 100, fee : 2});
db.transactions.insert({ cr_dr : "C", amount : 10, fee : 2});
db.transactions.insert({ cr_dr : "D", amount : 100, fee : 4});
db.transactions.insert({ cr_dr : "D", amount : 10, fee : 2});
db.transactions.insert({ cr_dr : "C", amount : 10, fee : 4});
db.transactions.insert({ cr_dr : "D", amount : 100, fee : 2});

使用 getIndexes() 方法可以檢視集合上的所有索引:

db.transactions.getIndexes();

內容解密:

  • getIndexes() 方法傳回一個陣列,包含集合上的所有索引資訊。
  • MongoDB 預設會在 _id 欄位上建立一個唯一的索引,以防止插入重複的 _id 值。

輸出結果顯示,transactions 集合上已經有一個預設的 _id_ 索引。

接下來,我們為 cr_dr 欄位建立一個升序索引:

db.transactions.createIndex({ cr_dr : 1 });

內容解密:

  • createIndex() 方法用於在指定欄位上建立索引。
  • { cr_dr : 1 } 表示在 cr_dr 欄位上建立升序索引。
  • createdCollectionAutomatically 表示是否自動建立了集合。
  • numIndexesBeforenumIndexesAfter 分別表示建立索引前後的索引數量。

再次執行 getIndexes() 方法,可以看到 transactions 集合上現在有兩個索引:預設的 _id_ 索引和我們剛剛建立的 cr_dr_1 索引。

我們也可以在建立索引時指定索引名稱:

db.transactions.createIndex({ cr_dr : -1 },{name : "index on cr_dr desc"})

內容解密:

  • { cr_dr : -1 } 表示在 cr_dr 欄位上建立降序索引。
  • {name : "index on cr_dr desc"} 指定了索引的名稱。

雜湊索引

MongoDB 也支援雜湊索引,適用於等值查詢,但不適用於範圍查詢。

db.transactions.createIndex({ cr_dr : "hashed" });

內容解密:

  • { cr_dr : "hashed" } 表示在 cr_dr 欄位上建立雜湊索引。
  • 雜湊索引在等值查詢上表現更好,但不支援範圍查詢。

索引刪除

如果知道索引的名稱,可以使用以下方法刪除索引:

db.collection.dropIndex('name_of_index');

如果不知道索引的名稱,可以使用以下方法刪除索引:

db.collection.dropIndex( { 'name_of_field' : -1 } );

內容解密:

  • dropIndex() 方法用於刪除指定的索引。
  • 可以根據索引的名稱或鍵值對來刪除索引。

稀疏索引和部分索引

稀疏索引(Sparse Indexes)和部分索引(Partial Indexes)是兩種特殊的索引型別。

稀疏索引

稀疏索引會忽略不存在指定欄位的檔案,因此可以用於建立唯一索引,同時允許某些檔案缺少該欄位。

db.scores.createIndex( { nickname: 1 } , { unique: true, sparse: true } )

內容解密:

  • { unique: true, sparse: true } 表示建立一個唯一的稀疏索引。
  • 稀疏索引可以節省儲存空間,並提高查詢效能。

部分索引

部分索引是 MongoDB 3.2 版本引入的新功能,它允許根據指定的篩選條件建立索引。

db.restaurants.createIndex(
{ cuisine: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)

內容解密:

  • { partialFilterExpression: { rating: { $gt: 5 } } } 表示只有當 rating 大於 5 時,才會將 cuisine 索引。
  • 部分索引提供了比稀疏索引更靈活的篩選條件。