跳到主要内容

db.collection.aggregate 阶段

2025年02月27日
柏拉文
越努力,越幸运

前言


聚合阶段文档

一、$addFields


1.1 认识

$addFields 为文档添加新字段。与 $project 类似,$addFields 重塑了流中的每个文档;具体来说,就是在输出文档中添加新字段,这些输出文档既包含输入文档中的现有字段,也包含新添加的字段。

1.2 语法

1.3 用法

二、$bucket


2.1 认识

$bucket 根据指定的表达式和存储桶边界将传入的文档分为多个组(称为存储桶)。

2.2 语法

2.3 用法

三、$bucketAuto


3.1 认识

$bucketAuto 根据指定的表达式,将接收到的文档归类到特定数量的群组中(称为“存储桶”)。自动确定存储桶边界,以尝试将文档均匀地分配到指定数量的存储桶中。

3.2 语法

3.3 用法

四、$changeStream


4.1 认识

$changeStream 返回集合的 Change Stream 游标。此阶段只能在 aggregation pipeline 中发生一次,并且必须作为第一阶段发生。

4.2 语法

4.3 用法

五、$changeStreamSplitLargeEvent


5.1 认识

$changeStreamSplitLargeEvent 将超过 16 MB 的大型 change stream 事件分割成较小的分段,在 change stream 游标中返回。

5.2 语法

5.3 用法

六、$collStats


6.1 认识

$collStats 返回有关集合或视图的统计信息。

6.2 语法

6.3 用法

七、$count


7.1 认识

$count 返回聚合管道此阶段的文档数量计数。

7.2 语法

7.3 用法

八、$densify


8.1 认识

$densify 在文档序列中创建新文档,其中缺少字段中的某些值。

8.2 语法

8.3 用法

九、$documents


9.1 认识

$documents 从输入表达式返回字面文档。

9.2 语法

9.3 用法

十、$facet


10.1 认识

$facet 在单个阶段内处理同一组输入文档上的多个聚合管道。支持创建多分面聚合,能够在单个阶段中跨多个维度或分面描述数据特征。理解: 在 MongoDB 中,$facet 是聚合管道的一个阶段,它允许你在同一个聚合查询中并行运行多个子管道, 可以并行处理, 每个子管道可以独立处理相同的一组输入文档,并生成各自的输出。这样你就可以一次性获得多种不同角度的聚合结果,而无需多次查询。 注意: $facet 在管道中的第一个阶段, 不会使用索引, 所以, 不要将 $facet 放到第一阶段。

10.2 语法

{ $facet:
{
<outputField1>: [ <stage1>, <stage2>, ... ],
<outputField2>: [ <stage1>, <stage2>, ... ],
...
}
}

10.3 用法

db.news.aggregate([
{
$match: {
$expr: { $eq: ["$userId", ObjectId("67c3cfd283b91d12a8aa61dd")] },
},
},
{
$facet: {
list: [{ $sort: { createdAt: -1 } }, { $limit: 20 }],
total: [{ $count: "count" }],
},
},
{
$project: {
list: 1,
total: { $arrayElemAt: ["$total.count", 0] },
},
},
]);
  • 子管道一: 根据 createdAt 排序, 并获取前 20 条文档。子管道一会输出 list 字段。

  • 子管道二: 计算 67c3cfd283b91d12a8aa61dd 下的文档总数。子管道二会输出 total 字段。

十一、$fill


11.1 认识

$fill 填充文档中的 null 和缺失的字段值。

11.2 语法

11.3 用法

十二、$geoNear


12.1 认识

$geoNear 根据与地理空间点的接近程度返回有序的文档流。针对地理空间数据,整合了 $match$sort$limit 功能。输出文档包含一个额外的距离字段,并可包含一个位置标识符字段。

12.2 语法

12.3 用法

十三、$graphLookup


13.1 认识

$graphLookup 对集合执行递归搜索。为每个输出文档添加一个新数组字段,其中包含该文档的递归搜索遍历结果。

13.2 语法

13.3 用法

十四、$group


14.1 认识

$group 按指定的标识符表达式对输入文档进行分组,并将累加器表达式(如果指定)应用于每个群组。接收所有输入文档,并为每个不同群组输出一个文档。输出文档仅包含标识符字段和累积字段(如果指定)。

14.2 语法

{
$group:
{
_id: <expression>, // Group key
<field1>: { <accumulator1> : <expression1> },
...
}
}
  • _id: 必需。_id 表达式指定群组键。 如果指定的 _id 值为空值或任何其他常量值,$group 阶段将返回聚合所有输入文档值的单个文档。

  • field: 可选。使用累加器操作符进行计算。

    • $sum: { $sum: <expression> } 或者 { $sum: [ <expression1>, <expression2> ... ] }{ $sum: 1 } 统计分组文档数量

    • $avg: { $avg: <expression> } 或者 { $avg: [ <expression1>, <expression2> ... ] }

    • $max: { $max: <expression> }

    • $min: { $min: <expression> }

    • $count: { $count: { } }, 不接受任何参数。$count 在功能上等同于在 $group 阶段使用 { $sum : 1 }

    • $multiply: { $multiply: [ <expression1>, <expression2>, ... ] }

14.3 用法

db.transactions.insertMany([ { symbol: "600519", qty: 100, price: 567.4, currency: "CNY" }, { symbol: "AMZN", qty: 1, price: 1377.5, currency: "USD" }, { symbol: "AAPL", qty: 2, price: 150.7, currency: "USD"} ]);

// 根据 currency 进行分组, currency 相同的为一组。因此, 不使用聚合操作符的情况下, $group 可以返回管道文档中某一字段的所有值(不重复)
db.transactions.aggregate([ { $group: { _id: "$currency" } } ]);

// 输出结果:
[ { _id: 'USD' }, { _id: 'CNY' } ]

// 根据 currency 进行分组, currency 相同的为一组。增加 totalQty 字段, 表示 qty 的总和; 增加 totalNotional 字段, 表示 价格 * 数量 的总和; 增加 avgPrice 字段, 表示 price 的平均值; 增加 count 字段, 表示这个分组有多少篇文档; 增加 maxNotional 字段, 求出该分组中 价格 * 数量 中的最大值; minNotional 同理。
db.transactions.aggregate([ { $group: { _id: "$currency", totalQty: { $sum: "$qty" }, totalNotional: { $sum: { $multiply: [ "$price", "$qty" ] } }, avgPrice: { $avg: "$price" }, count: { $sum: 1 }, maxNotional: { $max: { $multiply: [ "$price", "$qty" ] } }, minNotional: { $min: {$multiply: [ "$price", "$qty" ] } } } } ]);
// 输出结果:
[
{
_id: 'USD',
totalQty: 3,
totalNotional: 1678.9,
avgPrice: 764.1,
count: 2,
maxNotional: 1377.5,
minNotional: 301.4
},
{
_id: 'CNY',
totalQty: 100,
totalNotional: 56740,
avgPrice: 567.4,
count: 1,
maxNotional: 56740,
minNotional: 56740
}
]

// 将整个集合分为一组, 再此基础上进行聚合操作
db.transactions.aggregate([ { $group: { _id: null, totalQty: { $sum: "$qty" }, totalNotional: { $sum: { $multiply: [ "$price", "$qty" ] } }, avgPrice: { $avg: "$price" }, count: { $sum: 1 }, maxNotional: { $max: { $multiply: [ "$price", "$qty" ] } }, minNotional: { $min: {$multiply: [ "$price", "$qty" ] } } } } ]);
// 输出结果为:

[
{
_id: null,
totalQty: 103,
totalNotional: 58418.9,
avgPrice: 698.5333333333333,
count: 3,
maxNotional: 56740,
minNotional: 301.4
}
]

十五、$indexStats


15.1 认识

$indexStats 返回有关集合的每个索引使用情况的统计信息。

15.2 语法

15.3 用法

十六、$limit


16.1 认识

$limit 将未修改的前 n 个文档传递到管道,其中 n 为指定的限制。对于每个输入文档,输出一个文档(针对前 n 个文档)或零个文档(前 n 个文档之后)。

16.2 语法

16.3 用法

$limit 筛选出管道内前 N 篇文档

// 新增几个文档
db.user1.insertMany([ { name: { firstName: "alice", lastName: "wong" }, balance: 50}, { name: { firstName: "bob", lastName: "yang" }, balance: 20}])

// $limit 筛选出管道内前 N 篇文档
db.user1.aggregate([ { $limit: 1} ]);

十七、$listSampledQueries


17.1 认识

$listSampledQueries 列出所有集合或特定集合的抽样查询。

17.2 语法

17.3 用法

十八、$listSearchIndexes


18.1 认识

$listSearchIndexes 返回指定集合上现有 Atlas Search 索引的信息。

18.2 语法

18.3 用法

十九、$listSessions


19.1 认识

$listSessions 列出活动时间足够长、足以传播到 system.sessions 集合的所有会话。

19.2 语法

19.3 用法

二十、$lookup


20.1 认识

$lookup 对同一数据库中的一个集合执行 左外连接, 以过滤 已连接 集合中的文档以便进行处理。$lookup 阶段向每个输入文档添加一个新的数组字段。这新的数组字段包含来自 已连接 集合的匹配文档。$lookup 阶段将这些重塑后的文档传递给下一阶段。

20.2 语法

单个条件联接的等值匹配: 要在输入文档中的字段与 已连接 集合文档中的字段之间执行等值匹配

{
$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: 在同一个数据库中指定待执行联接操作的集合。from 是可选的,您可以改为在 $documents 阶段中使用 $lookup 阶段。从 MongoDB 5.1 开始,可以对 from 参数中指定的集合进行分片。

  • localField: 指定本地文档的 localField 对外部文档的 foreignField 执行等值匹配。如果本地文档不包含 localField 值,则 将使用 $lookup null 值进行匹配。

  • foreignField: 指定外部文档的 foreignField (指定来自 from 集合中的文档的字段)对本地文档的 localField 执行等值匹配。如果外部文档不包含 foreignField 值,则 将使用 $lookup null 值进行匹配。

  • as: 指定要添加到输入文档中的新数组字段的名称。新数组字段包含来自 from 集合的匹配文档。如果输入文档中已存在指定的名称,现有字段将被重写。

关联管道子查询联接: 在 MongoDB 中,关联子查询是 $lookup 阶段中的管道,引用了联接集合中的文档字段。

{
$lookup:
{
from: <joined collection>,
let: { <var_1>: <expression>,, <var_n>: <expression> },
pipeline: [ <pipeline to run on joined collection> ],
as: <output array field>
}
}
  • form: 在同一个数据库中指定待执行联接操作的集合。from 是可选的,您可以改为在 $documents 阶段中使用 $lookup 阶段。从 MongoDB 5.1 开始,from 集合可以分片。

  • let: 可选。 指定要在管道阶段 外部文档 使用的变量。使用变量表达式访问权限输入到 pipeline 的文档字段。let 中如果要引用 当前(自身)文档 管道阶段的变量、或者 当前(自身)文档 的字段名, 需要使用 $<variable> 字段表达式。如果, let 定义好的变量要在 pipeline 中引用, pipeline 需要使用 $$<variable> 来引用 let 变量。

  • pipeline: 指定在外部集合上运行的 pipelinepipeline 返回外部集合的文档。要返回所有文档,请指定一个空的 pipeline []pipeline 无法直接访问当前 当前(自身)文档 字段。 需要使用 let 选项为 当前(自身)文档 字段定义变量,然后在 pipeline 阶段引用 当前(自身)文档 变量。let 定义好的变量要在 pipeline 中引用, pipeline 需要使用 $$<variable> 来引用 let 变量。也就是说: pipeline 运行的是 外部文档, let 声明的是 当前(自身)文档, 外部文档 不能直接访问 当前(自身)文档

    • pipeline $match 阶段: pipeline $match 阶段 需要使用 $expr 来允许在 pipeline $match 阶段 中使用聚合表达式, 进而

      {
      let: { price: "$selfPrice" }, // price 为 let 定义的变量, $selfPrice 是自身集合的字段名 price, 但是, 在 let 中访问需要加 $

      pipeline: [ { $match: { $expr: { $eq: ["$$price", "$price"] } } } ], // 比较当前自身文档的 $$pricer 与 外部文档的 $price 是否相等。
      }
    • 管道中的其他(非 $match)阶段不需要 $expr 操作符符来访问权限变量。

  • as: 指定要添加到已连接文档的新数量字段的名称。新的大量字段包含来自加入的收集的匹配文档。如果指定的名称已存在于所连接的文档中,则现有字段将被覆盖。

非关联管道子查询联接: 在 MongoDB 中,非关联子查询是 $lookup 阶段中的管道, 没有引用联接集合中的文档字段。

{
$lookup:
{
from: <joined collection>,
pipeline: [ <pipeline to run on joined collection> ],
as: <output array field>
}
}

关联管道子查询联接并进行单条件等值匹配:

{
$lookup:
{
from: <foreign collection>,
localField: <field from local collection's documents>,
foreignField: <field from foreign collection's documents>,
let: { <var_1>: <expression>,, <var_n>: <expression> },
pipeline: [ <pipeline to run> ],
as: <output array field>
}
}

20.3 用法

单个条件联接的等值匹配: localField 字段值会与 foreignField 字段值进行等值匹配

db.user1.insertMany([ { name: { firstName: "alice", lastName: "wong" }, balance: 50, currency: ["CNY", "USD"]}, { name: { firstName: "bob", lastName: "yang" }, balance: 20, currency: ["GBP"]}, { name: { firstName: "charlie", lastName: "gordon" }, balance: 100},{ name: { firstName: "david", lastName: "wu" }, balance: 200, currency: []}, { name: { firstName: "eddie", lastName: "kim" }, balance: 20, currency: null} ]);

db.forex.insertMany([ { ccy: "USD", rate: 6.91, date: new Date("2018-12-21" ) }, { ccy: "GBP", rate: 8.72, date: new Date("2018-08-21" ) }, { ccy: "CNY", rate: 1.0, date: new Date("2018-12-21" ) } ]);

//
db.user1.aggregate([ { $lookup: { from: "forex", localField: "currency", foreignField: "ccy", as: "forexData" } } ]);

// 输出结果为:
[
{
_id: ObjectId('67bf93c73d5b2b20ce11cbca'),
name: { firstName: 'alice', lastName: 'wong' },
balance: 50,
currency: [ 'CNY', 'USD' ],
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
},
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf93c73d5b2b20ce11cbcb'),
name: { firstName: 'bob', lastName: 'yang' },
balance: 20,
currency: [ 'GBP' ],
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd3'),
ccy: 'GBP',
rate: 8.72,
date: ISODate('2018-08-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbcf'),
name: { firstName: 'charlie', lastName: 'gordon' },
balance: 100,
forexData: []
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbd0'),
name: { firstName: 'david', lastName: 'wu' },
balance: 200,
currency: [],
forexData: []
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbd1'),
name: { firstName: 'eddie', lastName: 'kim' },
balance: 20,
currency: null,
forexData: []
}
]

//
db.user1.aggregate([ { $unwind: { path: "$currency" }}, { $lookup: { from: "forex", localField: "currency", foreignField: "ccy", as: "forexData" } }]);

[
{
_id: ObjectId('67bf93c73d5b2b20ce11cbca'),
name: { firstName: 'alice', lastName: 'wong' },
balance: 50,
currency: 'CNY',
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf93c73d5b2b20ce11cbca'),
name: { firstName: 'alice', lastName: 'wong' },
balance: 50,
currency: 'USD',
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf93c73d5b2b20ce11cbcb'),
name: { firstName: 'bob', lastName: 'yang' },
balance: 20,
currency: 'GBP',
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd3'),
ccy: 'GBP',
rate: 8.72,
date: ISODate('2018-08-21T00:00:00.000Z')
}
]
}
]

非关联管道子查询联接

db.user1.insertMany([ { name: { firstName: "alice", lastName: "wong" }, balance: 50, currency: ["CNY", "USD"]}, { name: { firstName: "bob", lastName: "yang" }, balance: 20, currency: ["GBP"]}, { name: { firstName: "charlie", lastName: "gordon" }, balance: 100},{ name: { firstName: "david", lastName: "wu" }, balance: 200, currency: []}, { name: { firstName: "eddie", lastName: "kim" }, balance: 20, currency: null} ]);
db.forex.insertMany([ { ccy: "USD", rate: 6.91, date: new Date("2018-12-21" ) }, { ccy: "GBP", rate: 8.72, date: new Date("2018-08-21" ) }, { ccy: "CNY", rate: 1.0, date: new Date("2018-12-21" ) } ]);

// $lookup 子查询不相关联接
db.user1.aggregate([ { $lookup: { from: "forex", pipeline: [ { $match: { date: new Date("2018-12-21") } } ] , as: "forexData"} } ]);

// 结果如下:

[
{
_id: ObjectId('67bf93c73d5b2b20ce11cbca'),
name: { firstName: 'alice', lastName: 'wong' },
balance: 50,
currency: [ 'CNY', 'USD' ],
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
},
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf93c73d5b2b20ce11cbcb'),
name: { firstName: 'bob', lastName: 'yang' },
balance: 20,
currency: [ 'GBP' ],
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
},
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbcf'),
name: { firstName: 'charlie', lastName: 'gordon' },
balance: 100,
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
},
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbd0'),
name: { firstName: 'david', lastName: 'wu' },
balance: 200,
currency: [],
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
},
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbd1'),
name: { firstName: 'eddie', lastName: 'kim' },
balance: 20,
currency: null,
forexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
},
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
}
]

关联管道子查询联接

db.user1.insertMany([ { name: { firstName: "alice", lastName: "wong" }, balance: 50, currency: ["CNY", "USD"]}, { name: { firstName: "bob", lastName: "yang" }, balance: 20, currency: ["GBP"]}, { name: { firstName: "charlie", lastName: "gordon" }, balance: 100},{ name: { firstName: "david", lastName: "wu" }, balance: 200, currency: []}, { name: { firstName: "eddie", lastName: "kim" }, balance: 20, currency: null} ]);
db.forex.insertMany([ { ccy: "USD", rate: 6.91, date: new Date("2018-12-21" ) }, { ccy: "GBP", rate: 8.72, date: new Date("2018-08-21" ) }, { ccy: "CNY", rate: 1.0, date: new Date("2018-12-21" ) } ]);

// $lookup 子查询相关联接
db.user1.aggregate([ { $lookup: { from: "forex", let: { bal: "$balance" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: [ "$date", new Date("2018-12-21" )] }, { $gt: [ "$$bal", 100 ]} ] } } } ], as: "froexData" } } ]);

// 输出结果

[
{
_id: ObjectId('67bf93c73d5b2b20ce11cbca'),
name: { firstName: 'alice', lastName: 'wong' },
balance: 50,
currency: [ 'CNY', 'USD' ],
froexData: []
},
{
_id: ObjectId('67bf93c73d5b2b20ce11cbcb'),
name: { firstName: 'bob', lastName: 'yang' },
balance: 20,
currency: [ 'GBP' ],
froexData: []
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbcf'),
name: { firstName: 'charlie', lastName: 'gordon' },
balance: 100,
froexData: []
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbd0'),
name: { firstName: 'david', lastName: 'wu' },
balance: 200,
currency: [],
froexData: [
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd2'),
ccy: 'USD',
rate: 6.91,
date: ISODate('2018-12-21T00:00:00.000Z')
},
{
_id: ObjectId('67bfa3853d5b2b20ce11cbd4'),
ccy: 'CNY',
rate: 1,
date: ISODate('2018-12-21T00:00:00.000Z')
}
]
},
{
_id: ObjectId('67bf9fdd3d5b2b20ce11cbd1'),
name: { firstName: 'eddie', lastName: 'kim' },
balance: 20,
currency: null,
froexData: []
}
]

20.4 联系

单个条件联接的等值匹配 等于于如下 SQL 语句

SELECT *, (
SELECT ARRAY_AGG(*)
FROM <collection to join>
WHERE <foreignField> = <collection.localField>
) AS <output array field>
FROM collection;

关联管道子查询联接 等于于如下 SQL 语句

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (
SELECT <documents as determined from the pipeline>
FROM <collection to join>
WHERE <pipeline>
);

关联管道子查询联接并进行单条件等值匹配 等同于如下 SQL 语句

SELECT *, <output array field>
FROM localCollection
WHERE <output array field> IN (
SELECT <documents as determined from the pipeline>
FROM <foreignCollection>
WHERE <foreignCollection.foreignField> = <localCollection.localField>
AND <pipeline match condition>
);

二十一、$match


21.1 认识

$match 对输入文档进行筛选, 筛选文档流以仅允许匹配的文档将未修改的文档传入下一管道阶段。$match 使用标准 MongoDB 查询。对于每个输入文档,输出一个文档(一个匹配项)或零个文档(无匹配项)。应该尽量在聚合管道的开始阶段应用 $match,这样可以减少后续阶段中需要处理的文档数量, 以优化聚合操作的性能。

21.2 语法

{ $match: { <query predicate> } }
  • 匹配查询:

    • { <field>: 指定值 }
  • 比较操作符:

    • { <field>: { $lt: <value> } }

    • { <field>: { $gt: <value> } }

    • { <field>: { $eq: <value> } }

    • { <field>: { $ne: <value> } }

    • { <field>: { $in: [<value1>, <value2>, ……] }}

    • { <field>: { $nin: [<value1>, <value2>, ……] }}

    • { <field>: { $lte: <value> } }

    • { <field>: { $gte: <value> } }

  • 逻辑操作符:

    • { <field>: { $not: { <operator-expression> }}}

    • { $or: [ {<expression>}, {<expression>}, ……]}

    • { $nor: [ {<expression>}, {<expression>}, ……]}

    • { $and: [ {<expression>}, {<expression>}, ……]} 或者 { <expression>, <expression>, …… }

  • 字段操作符

    • { <field>: { $exists: <boolean> }}

    • { <field>: { $type: [<BSON type1>, <BSON type2>, ……]}}

  • 数组操作符:

    • { <field>: { $all: [ <value1>, <value2> …… ]}}

    • { <field>: { $elemMatch: { <query1>, <query2>, ……}}}

  • 运算操作符

    • { <field>: { $regex: /pattern/, $options: <options> } }

21.3 用法

$match 对输入文档进行筛选

// 新增几个文档
db.user1.insertMany([ { name: { firstName: "alice", lastName: "wong" }, balance: 50}, { name: { firstName: "bob", lastName: "yang" }, balance: 20}])

// $match 对文档进行筛选
db.user1.aggregate([ { $match: { "name.firstName": { $eq: "alice" }} } ]);
db.user1.aggregate([ { $match: { $or: [ { balance: { $gt: 40, $lt: 80 }}, { "name.lastName": { $regex: /^y/ }}] } }]);


// 将 $match 管道的筛选结果传递给 $project 管道
db.user1.aggregate([ { $match: { $or: [ { balance: { $gt: 40, $lt: 80 }}, { "name.lastName": { $regex: /^y/ }}] } }, { $project: { _id: 0}}]);

二十二、$merge


22.1 认识

$mergeaggregation pipeline 的结果文档写入集合。该阶段可将结果(插入新文档、合并文档、替换文档、保留现有文档、操作失败、使用自定义更新管道处理文档)纳入输出集合。要使用 $merge 阶段,它必须是管道中的最后一个阶段。

22.2 语法

22.3 用法

二十三、$out


23.1 认识

$outaggregation pipeline 的结果文档写入集合。要使用 $out 阶段,它必须是管道中的最后一个阶段。

23.2 语法

{ $out: "<output-collection>" } 

{ $out: { db: "<output-db>", coll: "<output-collection>" } }

{ $out:
{ db: "<output-db>", coll: "<output-collection>",
timeseries: {
timeField: "<field-name>",
metaField: "<field-name>",
granularity: "seconds" || "minutes" || "hours" ,
}
}
}

23.3 用法

db.transactions.insertMany([ { symbol: "600519", qty: 100, price: 567.4, currency: "CNY" }, { symbol: "AMZN", qty: 1, price: 1377.5, currency: "USD" }, { symbol: "AAPL", qty: 2, price: 150.7, currency: "USD"} ]);

// 第一次, output 集合不存在
db.transactions.aggregate([ { $group: { _id: "$currency", symbols: { $push: "$symbol" } } }, { $out: "output" } ]);

// 第二次, output 集合存在, 直接覆盖之前的集合
db.transactions.aggregate([ { $group:{ _id: "$symbol", totalNotional: { $sum: { $multiply: [ "$price", "$qty" ] } } } },{ $out: "output" } ]);

二十四、$planCacheStats


24.1 认识

$planCacheStats 返回集合的计划缓存信息。

24.2 语法

24.3 用法

二十五、$project


25.1 认识

$project 对输入文档再次投影,重塑流中的每个文档。可以用来灵活的控制输出文档的格式, 也可以用来剔除不相关的字段,以优化聚合管道操作的性能。例如添加新的字段或删除现有字段。对于每个输入文档,输出一个文档。$project 采用的文档可以指定包含字段、抑制 _id 字段、添加新字段以及重置现有字段的值。您也可以指定排除字段。

25.2 语法

{ $project: { <specification(s)> } }
  • <field>: <1 or true>: 指定包含字段。非零整数也被视为 true

  • _id: <0 or false>: 指定抑制 _id 字段。要有条件地排除字段,请改用 REMOVE 变量。

  • <field>: <expression>: 添加新字段或重置现有字段的值。如果表达式的计算结果为 $REMOVE,则在输出中排除该字段。

  • <field>: <0 or false>: 指定排除某个字段。 要有条件地排除字段,请改用 REMOVE 变量。如果指定排除 _id 以外的字段,则不能使用任何其他 $project 规范形式。此限制应用用于使用 REMOVE 变量有条件地排除字段。

25.3 用法

$project 对输入文档再次投影

// 新增几个文档
db.user1.insertMany([ { name: { firstName: "alice", lastName: "wong" }, balance: 50}, { name: { firstName: "bob", lastName: "yang" }, balance: 20}])

// $project 重新投影
db.user1.aggregate([ { $project: { _id: 0, balance: 1, clientName: "$name.firstName", nameArray: ["$name.firstName", "$name.lastName"] } } ]);

二十六、$querySettings


26.1 认识

$querySettings 返回之前使用 setQuerySettings 添加的查询设置。

26.2 语法

26.3 用法

二十七、$queryStats


27.1 认识

$queryStats 返回已记录查询的运行时统计信息。

27.2 语法

27.3 用法

二十八、$redact


28.1 认识

$redact 根据文档中存储的信息来限制每个文档的内容,从而重塑流中的每个文档。结合了 $project$match 的功能。可用于实现字段级别访问控制。对于每个输入文档,输出一个或零个文档。

28.2 语法

28.3 用法

二十九、$replaceRoot


29.1 认识

$replaceRoot 用指定的嵌入文档替换文档。该操作会替换输入文档中的所有现有字段,包括 _id 字段。指定嵌入在输入文档中的文档,以将嵌入的文档提升到顶层。

29.2 语法

29.3 用法

三十、$replaceWith


30.1 认识

$replaceWith 用指定的嵌入文档替换文档。该操作会替换输入文档中的所有现有字段,包括 _id 字段。指定嵌入在输入文档中的文档,以将嵌入的文档提升到顶层。

30.2 语法

30.3 用法

三十一、$sample


31.1 认识

$sample 从其输入中随机选择指定数量的文件。

31.2 语法

31.3 用法

三十二、$search


32.1 认识

$search 对 Atlas 集合中的一个或多个字段执行全文搜索。

32.2 语法

32.3 用法

三十三、$searchMeta


33.1 认识

$searchMeta 返回对 Atlas 集合进行 Atlas Search 查询时,得到的不同类型的元数据结果文档。

33.2 语法

33.3 用法

三十四、$set


34.1 认识

$set 为文档添加新字段。与 $project 类似,$set 重塑了流中的每个文档;具体来说,就是在输出文档中添加新字段,这些输出文档既包含输入文档中的现有字段,也包含新添加的字段。

34.2 语法

34.3 用法

三十五、$setWindowFields


35.1 认识

$setWindowFields 将文档分组到窗口中,并将一个或多个操作符应用于每个窗口中的文档。

35.2 语法

35.3 用法

三十六、$skip


36.1 认识

$skip 跳过前 n 个文档,其中 n 是指定的跳过编号,并将未修改的剩余文档传递到管道。对于每个输入文档,输出零个文档(对于前 n 个文档)或一个文档(如果在前 n 个文档之后)。

36.2 语法

36.3 用法

$skip 跳过管道内前 N 篇文档:

// 新增几个文档
db.user1.insertMany([ { name: { firstName: "alice", lastName: "wong" }, balance: 50}, { name: { firstName: "bob", lastName: "yang" }, balance: 20}])

// $skip 跳过管道内前 N 篇文档
db.user1.aggregate([ { $skip: 1} ]);

三十七、$sort


37.1 认识

$sort 按指定的排序键对文档流重新排序。仅顺序会改变,而文档则保持不变。对于每个输入文档,输出一个文档。如果对多个字段进行排序,则按从左到右的顺序进行排序。例如,在上面的表单中,文档首先按 <field1> 排序。然后,具有相同 <field1> 值的文档将按 <field2> 进一步排序。

37.2 语法

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
  • 1: 升序排序。

  • -1: 降序排序。

  • { $meta: "textScore" }: 按计算的 textScore 元数据降序排序顺序。

37.3 用法

$sort 对输入文档进行排序

db.user1.aggregate([ { $sort: { balance: 1, "name.lastName": -1 } } ]);

三十八、$sortByCount


38.1 认识

$sortByCount 根据指定表达式的值对传入文档进行分组,然后计算每个不同群组中的文档数量。

38.2 语法

38.3 用法

三十九、$unionWith


39.1 认识

$unionWith 执行两个集合的联合;即将两个集合的管道结果合并到一个结果集中。

39.2 语法

39.3 用法

四十、$unset


40.1 认识

$unset 从文档中删除/排除字段。

40.2 语法

40.3 用法

四十一、$unwind


41.1 认识

$unwind 对输入文档中的某一数组字段进行解构,以便为每个元素输出文档。每个输出文档均会将此数组替换为元素值。对于每个输入文档,输出 n 个文档,其中 n 为数组元素的数量,且对于空数组可为零。

41.2 语法

{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
  • path: 数组字段的字段路径。如需指定字段路径,请在字段名称前加上美元符号 $,并用引号括起来。

  • includeArrayIndex: 可选。新字段的名称,用于保存该元素的数组索引。名称不能以美元符号 $ 开头。

  • preserveNullAndEmptyArrays: 可选。如果 true ,如果 pathnull、缺失或空大量,$unwind 会输出文档。false 如果为 path ,如果 为 null、缺失或空大量,则 $unwind 不会输出文档。默认值为 false

41.3 用法

$unwind 展开输入文档中的数组字段

db.user1.update({ "name.firstName": "alice"}, { $set: { currency: ["CNY", "USD"]}});
db.user1.update({ "name.firstName": "bob"}, { $set: { currency: ["GBP"]}});
db.user.insertMany([ { name: { firstName: "charlie", lastName: "gordon" }, balance: 100},{ name: { firstName: "david", lastName: "wu" }, balance: 200, currency: []}, { name: { firstName: "eddie", lastName: "kim" }, balance: 20, currency: null} ]);

// $unwind 展开输入文档中的数组字段, 将数组字段中的每个元素新建文档,新文档跟原文档一模一样, 唯一的区别是数组字段值变为每一个元素。
db.user1.aggregate([ { $unwind: { path: "$currency", includeArrayIndex: 'ccyIndex', preserveNullAndEmptyArrays: false } } ]);

// 结果如下:

[
{
_id: ObjectId('67bf93c73d5b2b20ce11cbca'),
name: { firstName: 'alice', lastName: 'wong' },
balance: 50,
currency: 'CNY',
ccyIndex: Long('0')
},
{
_id: ObjectId('67bf93c73d5b2b20ce11cbca'),
name: { firstName: 'alice', lastName: 'wong' },
balance: 50,
currency: 'USD',
ccyIndex: Long('1')
},
{
_id: ObjectId('67bf93c73d5b2b20ce11cbcb'),
name: { firstName: 'bob', lastName: 'yang' },
balance: 20,
currency: 'GBP',
ccyIndex: Long('0')
}
]

四十二、$vectorSearch


42.1 认识

$vectorSearch 对 Atlas 集合的指定字段中的向量执行 ANN 或 ENN 搜索。

42.2 语法

42.3 用法