HOWTO · MongoDB

MongoDB 中的 SELECT COUNT GROUP BY

本教程將介紹 MongoDB 中的操作以及 MongoDB 中聚合操作的使用。此外,這將詳細討論如何使用聚合函式對組進行計數和排序。

本頁內容

若要依欄位、欄位組合或日期計算文件數量,請使用 MongoDB 聚合管道。管道中的階段會依序轉換文件;$group 會設定每個輸出群組的 _id,而 $sum: 1 會計算進入該群組的文件數量。計算陣列元素前先加入 $unwind,再用 $sort 排序結果。

以下範例使用 students 集合,其中有 name、city 和 courses 欄位;日期範例則使用另一個 sales 集合。請將這些名稱替換成你的資料所用的集合與欄位。

依單一欄位使用 $group 計算文件數量

依你想查看其不重複值的欄位分組。例如,db.students.aggregate([{$group: {_id: "$city", count: {$sum: 1}}}]) 會為每個城市產生一筆結果。每筆結果的 _id 是城市值,count 則是該城市的文件數量。$sum: 1 會為每個進入群組的文件加一;它不會計算另一個欄位的不重複值。

以下使用一般 Python 值,以精簡方式示範這種計數模式。這只是語言層級的說明,不是 MongoDB 查詢,也不是執行結果證據。

執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

students = [{"city": "Canada"}, {"city": "Canada"}, {"city": "Japan"}]
counts = {}
for student in students:
    city = student["city"]
    counts[city] = counts.get(city, 0) + 1

結果對應表會將 Canada 對應至 2,並將 Japan 對應至 1。在 MongoDB 中,相應的群組鍵是欄位路徑 "$city"。

依多個欄位計算文件數量

若要計算每種欄位組合的文件數量,請將包含所有分組欄位的物件設為 _id。例如,db.students.aggregate([{$group: {_id: {city: "$city", name: "$name"}, count: {$sum: 1}}}]) 會計算每個城市與姓名組合的文件數量。MongoDB 會將整個 _id 物件視為群組鍵,因此任一值不同就會形成不同群組。你可以在 _id 中新增或移除屬性來調整組合,無須變更計數運算式。

計算文件數量時,請在 $group 中使用 $sum: 1。$count 階段則會計算到達該階段的文件數量,並輸出一個計數欄位;若需要為每個群組各自計數,它不能取代累加器。

使用 $unwind 和 $sortByCount 計算陣列值

計算陣列欄位中的元素前,必須先將它們轉成個別值。管道 db.students.aggregate([{$unwind: "$courses"}, {$sortByCount: "$courses"}]) 會為每個課程元素輸出一筆管道文件,接著將相同課程值分組,並依計數由高至低排序。某位學生有三門課程,就會分別為這三門課程各貢獻一次計數。

$sortByCount 是一種簡寫:依運算式分組、使用 $sum: 1 累加,並依計數遞減排序。它計算的是出現次數;如果同一門課程在某位學生的陣列中重複出現,就不等同於計算該課程的不重複學生人數。若需要區分這兩種結果,請在展開陣列前先去重,或先依學生和課程分組來建立管道。

排序分組計數結果

在 $group 之後加入 $sort,即可明確指定排序方式。對於依單一欄位分組的城市結果,{$sort: {_id: 1}} 會依城市鍵遞增排序,而 {$sort: {_id: -1}} 會依遞減排序。對於複合群組,可以依巢狀鍵排序,例如 {"_id.city": 1}。若要依出現頻率排名,請使用 {$sort: {count: -1}};1 代表遞增,-1 代表遞減。

例如,db.students.aggregate([{$group: {_id: "$city", count: {$sum: 1}}}, {$sort: {count: -1, _id: 1}}]) 會先列出數量最多的群組,並以城市鍵作為同分時的排序依據。若依分組輸出中不存在的欄位排序,就無法得到預期順序;因此請依 _id 或 count 等累加結果排序。

依日期分組並計算數量與總額

分組前先篩選所需的日期區間,將每個時間戳正規化為日曆日期,再於 $group 中同時計算數量和總額。以下管道示範如何依 UTC 日期處理 2021 年資料:db.sales.aggregate([{$match: {date: {$gte: ISODate("2021-01-01T00:00:00Z"), $lt: ISODate("2022-01-01T00:00:00Z")}}}, {$group: {_id: {$dateToString: {format: "%Y-%m-%d", date: "$date", timezone: "UTC"}}, count: {$sum: 1}, totalSales: {$sum: {$multiply: ["$price", "$quantity"]}}}}, {$sort: {_id: 1}}])。

下界包含在範圍內,而次年年初則不包含,因此會納入 2021 年內的時間戳,排除 2022 年的資料。$dateToString 會將同一個 UTC 日期的時間戳歸入同一群組。$sum: 1 會計算銷售文件數量;$multiply 會計算每筆明細的價格乘以數量,再由 $sum 加總。請使用符合報表日期的時區;當地營業日可能與 UTC 日期不同。請確認 date、price 和 quantity 分別具有合適的日期與數值,並依需求調整日期區間。