分享於

簡介

MongoDB 是一種以文件為基礎的 NoSQL 資料庫,其中的資料是以集合的方式組織,而集合是由 JSON 文件組成。如同任何資料庫,MongoDB 也有使用者可用來存取資料的語言。以 MongoDB 來說,這種語言就是 MongoDB 查詢語言,或簡稱 MQL。無論是 MQL 或 SQL,資料庫查詢一開始可能很簡單,但隨著資料庫擴展,就會出現更複雜的查詢。

MongoDB 聚合框架是一種查詢 MongoDB 文件的方式,可以分解這些更令人困惑的查詢。它將複雜的邏輯分解為循序操作。在本指南中,我們將介紹 MongoDB 聚合框架、討論常見的聚合階段,並以一個簡單的聚合管道範例作結。

MongoDB 聚合框架如何運作?

MongoDB 聚合框架的目的是設計一個由多個階段組成的管道,以處理文件。您從集合的資料開始,在管道的每個階段之後,您會更接近最終結果,也就是所需的檔案。

每個階段都會對文件執行操作。可以執行多種操作。例如,階段可以篩選、分組,甚至計算資料的值。在每個階段之後,輸出的文件會傳遞到下一個階段,依此類推,直到沒有剩餘階段為止。

透過聚合框架,可以實現多個目標。我們將使用實際的操作語法深入探討具體範例,但在理論上,書店小說部門的分析師可以設定一個框架,根據類型或作者將購買數量分組,以告知銷售樓層。他們能夠透過新增階段來迭代查詢,直到資料正是他們正在尋找的內容。無論團隊為何,都可以從資料中獲得見解,而透過聚合管道的組成,可以更輕鬆地發現這些見解。

最常見的 MongoDB 聚合操作有哪些?

在撰寫本文時,MongoDB 框架中大約有 38 個可用的聚合階段。我們不會在本指南中深入探討所有階段,但您可以在官方 MongoDB 文件中查看完整列表。我們將花一些時間重點介紹一些也將在範例管道中使用的階段。

  • $project:重新塑造串流中的每個文件,例如透過新增欄位或移除現有欄位。對於每個輸入文件,輸出一個文件。
  • $match:篩選文件串流,僅允許符合條件的文件未經修改地傳遞到下一個管道階段。$match 使用標準 MongoDB 查詢。對於每個輸入文件,輸出一個文件(符合條件)或零個文件(不符合條件)。
  • $group:依指定的識別碼表達式將輸入文件分組,並將累加器表達式(如果指定)套用至每個群組。消耗所有輸入文件,並為每個不同的群組輸出一個文件。輸出文件僅包含識別碼欄位,以及累加欄位(如果指定)。
  • $sort:依指定的排序鍵重新排序文件串流。僅順序變更;文件保持未修改。對於每個輸入文件,輸出一個文件。
  • $skip:略過前 n 個文件,其中 n 是指定的略過數字,並將剩餘的文件未經修改地傳遞到管道。對於每個輸入文件,輸出零個文件(對於前 n 個文件)或一個文件(如果在前 n 個文件之後)。
  • $limit:將前 n 個文件未經修改地傳遞到管道,其中 n 是指定的限制。對於每個輸入文件,輸出一個文件(對於前 n 個文件)或零個文件(在前 n 個文件之後)。
  • $unwind:解構輸入文件中的陣列欄位,為每個元素輸出一個文件。每個輸出文件都會將陣列取代為元素值。對於每個輸入文件,輸出 n 個文件,其中 n 是陣列元素的數量,空陣列則為零。

實際運作的聚合管道

為了透過實際範例將聚合具體化,我們將逐步設定一個虛構書店的管道。我們將從一些庫存訂單資料開始,並建立一個管道,採用這些原始資料並輸出哪些作者有多個訂單,以及訂購了他們書籍的多少副本。

首先,我們將一些範例訂單文件插入到 bookOrders 集合中。

db.bookOrders.insertMany ( [
{ _id: 0, first_name: "Fyodor", last_name: "Dostoyevsky", book_title: 'Demons', genre: 'Fiction', quantity: 10, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 1, first_name: "Fyodor", last_name: "Dostoyevsky", book_title: 'Brothers Karamosov', genre: 'Fiction', quantity: 25, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 2, first_name: "Jacques", last_name: "Derrida", book_title: 'The Politics of Friendship', genre: 'Fiction', quantity: 5, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 3, first_name: "Charles", last_name: "Dickens", book_title: 'Tale of Two Cities', genre: 'Fiction', quantity: 6, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 4, first_name: "James", last_name: "Joyce", book_title: 'Ulysses', genre: 'Fiction', quantity: 30, date: ISODate( "2021-03-13T11:19:30Z" ) },
{ _id: 5, first_name: "Henry David", last_name: "Thoreau", book_title: 'Walden', genre: 'Nonfiction', quantity: 15, date: ISODate( "2021-03-13T11:19:30Z" ) },
{ _id: 6, first_name: "Virginia", last_name: "Woolf", book_title: "A Room of One's Own", genre: 'Nonfiction',
quantity: 18, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 7, first_name: "Virginia", last_name: "Woolf", book_title: "Mr's Dalloway", genre: 'Fiction', quantity: 14, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 8, first_name: "Zadie", last_name: "Smith", book_title: 'White Teeth', genre: 'Fiction', quantity: 8, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 9, first_name: "Charles", last_name: "Dickens", book_title: 'The Old Curiousity Shop', genre: 'Fiction', quantity: 6, date: ISODate( "2022-10-21T11:19:30Z" ) }
] )

現在我們的集合有一些範例文件,我們可以開始查詢了。聚合管道使用 db.<collection-name>.aggregate() 方法執行。我們的目標是設計一個查詢,傳回一份作者列表,這些作者的小說書籍總副本訂購量最多。範例聚合查詢可以在下方找到,並描述了每個階段。

db.bookOrders.aggregate ( [
// Stage 1: The $match operator scans the collection for documents
matching the specified condition to pass to the next stage.
{
$match:
{
genre: "Fiction"
}
},
// Stage 2: The $project operator specifies which fields
in the matched documents should pass onto the next stage.
{
$project:
{
last_name : 1,
quantity : 1
}
},
// Stage 3: The $group operator groups the documents by the specified expression
and outputs a document for each unique grouping. The _id field specifies the distinct key to group by.
{
$group:
{
_id: "$last_name",
totalQuantity: { $sum: "$quantity" } }
},
// Stage 4: The $sort operator specifies the field(s) to sort by and the order.
-1 specifies a descending order and 1 specifies ascending order.
{
$sort:
{ totalQuantity: -1 }
}
] )

執行我們的聚合查詢後,我們得到以下輸出

[
{ _id: 'Dostoyevsky', totalQuantity: 35 },
{ _id: 'Joyce', totalQuantity: 30 },
{ _id: 'Woolf', totalQuantity: 14 },
{ _id: 'Dickens', totalQuantity: 12 },
{ _id: 'Smith', totalQuantity: 8 },
{ _id: 'Derrida', totalQuantity: 5 }
]

這個範例是刻意簡化的,但它示範了聚合管道如何從某些查詢中消除一些複雜性。達到所需輸出的每個步驟都清楚地分解並劃分到一個清晰的階段中。

根據集合和文件資料結構,在建置聚合管道時,有一些最佳化需要考慮。此外,此框架可能不適用於所有複雜邏輯。這取決於具體情況。

應該指出的一個小最佳化可以在我們範例的前兩個階段中看到。通常,$match 運算子用於開始大多數管道,並且是最佳實務。但是,如果您的集合充滿非常大的文件,則建議改為從 $project 運算子開始。從 $project 開始,限制了在管道中較早階段傳遞到下一個階段的欄位數量,並減少了一些不必要的負載。

結論

在本文中,我們介紹了 MongoDB 的聚合框架。我們討論了它是什麼,以及它如何成為簡化複雜邏輯和冗長查詢的工具。聚合管道的階段將邏輯分解為易於遵循和操作的區塊。

聚合管道簡化了資料存取,了解其運作方式非常重要。MongoDB 的聚合框架可以用來做比我們在書店範例中示範的更多事情,我們希望這個簡介能引導您進一步探索。

關於作者
Alex Emerich

Alex Emerich

Alex 是典型的賞鳥、熱愛嘻哈音樂的書蟲,也喜歡撰寫關於資料庫的文章。他目前住在柏林,在那裡可以看到他像李奧波德·布魯姆一樣在城市中漫無目的地漫步。