上篇最后說到管道操作符,本篇文章將詳細說一下管道操作符。
什么是管道操作符(Aggregation Pipeline Operators)
mongoDB有4類操作符用于文檔的操作,例如find查詢里面會用到的in等。操作符以$開頭,分為查詢操作符,更新操作符,管道操作符,查詢修飾符4大類。其中管道操作符是用于聚合管道中的操作符。
管道操作符的分類
管道操作符可以分為三類:
- 階段操作符(Stage Operators)
- 表達式操作符(Expression Operators)
- 累加器(Accumulators)
此處中文勉強翻譯,以英文為準,歡迎大神給意見,謝謝。
參考MongoDB官網:https://docs.mongodb.com/manual/reference/operator/aggregation/#accumulators
階段操作符(Stage Operators)
階段操作符是使用于db.collection.aggregate方法里面,數組參數中的第一層。
db.collection.aggregate( [ { 階段操作符:表述 }, { 階段操作符:表述 }, ... ] )
表達式操作符(Expression Operators)
表達式操作符主要用于在管道中構建表達式時使用,使用類似于函數那樣需要參數,主要用于$project操作符中,用于構建表達式,使用方法一般如下:
方法1:
{ <operator>: [ <argument1>, <argument2> ... ] }
方法2:
{ <operator>: <argument> }
累加器(Accumulators)
累加器本來只能使用與project。當在
project中使用時,累加器則是針對每個字面量起作用,具體用法下一篇文章闡述。
由于操作符比較多,本篇文章先說第一類階段操作符,后面兩類之后再說。
常用階段操作符
操作符 | 簡述 |
---|---|
$match | 匹配操作符,用于對文檔集合進行篩選 |
$project | 投射操作符,用于重構每一個文檔的字段,可以提取字段,重命名字段,甚至可以對原有字段進行操作后新增字段 |
$sort | 排序操作符,用于根據一個或多個字段對文檔進行排序 |
$limit | 限制操作符,用于限制返回文檔的數量 |
$skip | 跳過操作符,用于跳過指定數量的文檔 |
$count | 統計操作符,用于統計文檔的數量 |
$group | 分組操作符,用于對文檔集合進行分組 |
$unwind | 拆分操作符,用于將數組中的每一個值拆分為單獨的文檔 |
$lookup | 連接操作符,用于連接同一個數據庫中另一個集合,并獲取指定的文檔,類似于populate |
更多操作符介紹詳見官網:https://docs.mongodb.com/manual/reference/operator/aggregation/
階段操作符詳解
假設有一個保存用戶的集合Users,一個文章的集合Articles,數據大致如下:
users:
[
{ name: 'John', age: 16, sex: male, city: guangzhou, _id: 1, ...},
{ name: 'Rose', age: 18, sex: female, city: beijing, _id: 2, ...},
{ name: 'Jack', age: 29, sex: male, city: guangzhou, _id: 3, ...},
{ name: 'Allen', age: 18, sex: female, city: beijing, _id: 4, ...},
{ name: 'Cruz', age: 22, sex: male, city: guangzhou, _id: 5, ...},
{ name: 'Peter', age: 18, sex: male, city: guangzhou, _id: 6, ...},
{ name: 'Kelly', age: 23, sex: female, city: shanghai, _id: 7, ...},
...
]
articles:
[
{ title: 'this is article A', author: 'John', _id: 1, ... },
{ title: 'this is article B', author: 'Jack', _id: 2, ... },
{ title: 'this is article C', author: 'Rose', _id: 3, ... },
{ title: 'this is article D', author: 'John', _id: 4, ... },
{ title: 'this is article E', author: 'John', _id: 5, ... },
...
]
$match 匹配操作符
說明:
用于對文檔集合進行篩選
用法:
{ $match: { <query> } }
示例:
- 查詢用戶年齡是18歲的用戶
db.users.aggregate([{ $match : { age : "18" } }]);
$project 投射操作符
說明:
用于重構每一個文檔的字段,可以提取字段,重命名字段,甚至可以對原有字段進行操作后新增字段
用法:
{ $project: { <specification(s)> } }
specification的規則
規則 | 描述 |
---|---|
<字段名>: 1 or true | 選擇需要返回什么字段 |
_id: 0 or false | 不返回_id(默認返回) |
<字段名>: 表達式 | 使用表達式,可以用于重命名字段,或對其值進行操作,或新增字段 |
<字段名>: 0 or false | 選擇需要不返回什么字段,注意:當使用這種用法時,就不要用上面的方法 |
示例1:
- 用戶集合投射用戶姓名
- 不返回_id
db.users.aggregate([{ $project : { name: 1 } }]);
示例2:
- 將_id重命名為userId
- 不返回id
db.users.aggregate([{ $project : { ueserId: '$_id', _id: 0 } }]);
示例3:
- 返回新字段username,并使用表達式讓它的值為name的大寫。
db.users.aggregate([
{
$project : {
name: 1,
username: { $toUpper: '$name' },
_id: 0
}
}
]);
關于管道表達式:最簡單的“
fieldname(如: { userId: '
toUpper)構成更豐富的表達式,將多個字面量和變量組合在一起使用,得到更多有意思的值,更多表達式操作符的說明及使用在另外的篇章中詳細闡述。
$sort 排序操作符
說明:
用于根據一個或多個字段對文檔進行排序
用法:
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
示例:
- users集合按照年齡age從低到高排序
db.users.aggregate([{ $sort : { age: 1 } }]);
$limit 限制操作符
說明:
用于限制返回文檔的數量
用法:
{ $limit: <positive integer> }
示例:
- 返回5篇article
db.articles.aggregate({ $limit : 3 });
$skip 跳過操作符
說明:
用于跳過指定數量的文檔
用法:
{ $skip: <positive integer> }
示例:
- 跳過1個文檔
db.users.aggregate([{ $skip : 1 }]);
$count 統計操作符
說明:
用于統計文檔的數量
用法:
{ $count: <string> }
string是統計之后輸出統計結果的字段名
示例:
- 統計文章的總數,以totalArticle返回
db.articles.aggregate([{ totalArticle : 1 }]);
$group 分組操作符
說明:
用于對文檔集合進行分組
用法:
{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }
_id是必須的,用作分組的依據條件
示例:
- 將用戶(users)按性別(sex)分組
db.users.aggregate([{ $group : { _id: '$sex' } }]);
返回結果:
[
{ _id: 'male' },
{ _id: 'female' }
]
進階示例:
- 將用戶(users)按性別(sex)分組
- 分組后使用計算各自性別的平均年齡
- 統計不同的性別的人數,并以count返回
db.users.aggregate([
{
$group : {
_id: '$sex',
avgAge: { $avg: '$age' },
conut: { $sum: 1 }
}
}
]);
返回結果:
[
{ _id: 'male', avgAge: <男性平均年齡>, count: <男性人數> },
{ _id: 'female', avgAge: <女性平均年齡>, count: <女性人數> }
]
此處用到的表達式 {
age' } 用于求平均年齡,
sum用于匯總, 都只能在
project中使用,詳細會在另外的篇章中闡述。
$unwind 拆分操作符
說明:
用于將數組中的每一個值拆分為單獨的文檔
用法:
{ $unwind: <field path> }
3.2+版本的用法:
增加icludeArrayIndex,preserveNullAndEmptyArrays兩個可選配置
{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
字段 | 類型 | 描述 |
---|---|---|
path | string | 必填,數組的字段名,指定需要拆分的字段 |
includeArrayIndex | string | 可選,定義返回的字段名,返回的值是拆分前值在原數組的位置 |
preserveNullAndEmptyArrays | boolean | 可選,配置在path的值為空或缺失的情況下是否拆分, 默認false |
示例:
假設articles文檔集合是這樣:
{ title: 'this is article A', author: 'John', _id: 1, comments: ['a', 'b', 'c']}
db.articles.aggregate([{ $unwind: '$comments' }]);
結果:
[
{ title: 'this is article A', author: 'John', _id: 1, comments: 'a'},
{ title: 'this is article A', author: 'John', _id: 1, comments: 'b'},
{ title: 'this is article A', author: 'John', _id: 1, comments: 'c'},
]
進階示例(v3.2+):
假設articles文檔集合是這樣:
[
{ title: 'this is article A', author: 'John', _id: 1, comments: ['a', 'b', 'c'] }
{ title: 'this is article B', author: 'Jack', _id: 2 },
{ title: 'this is article C', author: 'Amy', _id: 3, comments: [] },
{ title: 'this is article D', author: 'Lam', _id: 4, comments: null },
]
操作:
db.articles.aggregate([
{
$unwind: {
path: '$comments',
includeArrayIndex: 'arrayIndex',
}
}
]);
結果:
[
{ title: 'this is article A', author: 'John', _id: 1, comments: 'a', arrayIndex: NumberLong(0) },
{ title: 'this is article A', author: 'John', _id: 1, comments: 'b', arrayIndex: NumberLong(1) },
{ title: 'this is article A', author: 'John', _id: 1, comments: 'c', arrayIndex: NumberLong(2) },
]
操作:
db.articles.aggregate([
{
$unwind: {
path: '$comments',
preserveNullAndEmptyArrays: true,
}
}
]);
結果:
[
{ title: 'this is article A', author: 'John', _id: 1, comments: 'a' },
{ title: 'this is article A', author: 'John', _id: 1, comments: 'b' },
{ title: 'this is article A', author: 'John', _id: 1, comments: 'c' },
{ title: 'this is article B', author: 'Jack', _id: 2 },
{ title: 'this is article C', author: 'Amy', _id: 3 },
{ title: 'this is article C', author: 'Amy', _id: 3, comments: null }
]
$lookup 連接操作符
說明:
用于連接同一個數據庫中另一個集合,并獲取指定的文檔,類似于populate
用法:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
字段 | 描述 |
---|---|
from | 需要關聯的集合名 |
localField | 本集合中需要查找的字段 |
foreignField | 另外一個集合中需要關聯的字段 |
as | 輸出的字段名 |
示例:
- ariticles中的author關聯到user表
- authoer字段返回詳細的用戶的信息
db.articles.aggregate([
{
$lookup:
{
from: "users",
localField: "author",
foreignField: "name",
as: "author"
}
}
])
結果:
[
{
title: 'this is article A',
author: {
name: 'John',
age: 16,
sex: male,
city: guangzhou,
_id: 1,
...
},
_id: 1,
...
},
{
title: 'this is article B',
author: {
name: 'Jack',
age: 29,
sex: male,
city: guangzhou,
_id: 3,
...
},
_id: 2,
...
},
{
title: 'this is article C',
author: {
name: 'Rose',
age: 18,
sex: male,
city: beijing,
_id: 2,
...
},
_id: 3,
...
},
{
title: 'this is article D',
author: {
name: 'John',
age: 16,
sex: male,
city: guangzhou,
_id: 1,
...
},
_id: 4,
...
},
{
title: 'this is article E',
author: {
name: 'John',
age: 16,
sex: male,
city: guangzhou,
_id: 1,
...
},
_id: 5,
...
},
...
]
綜合示例
需求
找出發表文章最多的5位作者,按發表文章排序,顯示他的發表文章的總次數,和他自己的信息
- 文章按照作者分組,統計次數
- 按照次數從高到低排序
- 截取頭5名
- 關聯用戶信息
- 不輸出文章_id
操作
db.articles.aggregate([
{
$group:
{
_id: "$author",
count: { $sum: 1 },
}
},
{
$sort: { count: -1 }
},
{
$skip: 5
},
{
$lookup:
{
from: "users",
localField: "author",
foreignField: "name",
as: "author"
}
},
{
$project: {
_id: 0,
}
}
])
總結
本文介紹了幾個使用聚合管道查詢時常用的管道操作符的用法,熟練地綜合使用以上操作符可以對數據進行多樣的處理,組合,統計,得出多樣化的數據。另外再加以配合表達式操作符(Expression Operators)組成的表達式, 或者在group中使用累加器(Accumulators)能查詢統計的內容會更加的多樣化。
感謝閱讀~