时间:2021-07-01 10:21:17 帮助过:3人阅读
mongodb 的聚合框架是基于数据处理管道的概念建模的,文档通过一个多阶段管道处理,转换为聚合结果
聚合管道使用本地操作实现了高效的数据聚合操作,是mongodb首选的数据聚合方法
聚合管道可以对分片集合进行操作
在聚合管道的某些阶段,可以使用索引来提高性能。此外,聚合管道有一个内部优化阶段
聚合管道由多个阶段(stage)组成,每个阶段都会对输入文档进行处理转换。管道阶段不需要为每个输入文档生成一个输出文档,因为有些阶段会生成新的文档或过滤掉文档
管道阶段可以在管道中出现多次,但 $out、$merge、$geoNear 阶段只能出现一次
计算集合或视图中数据的聚合结果
游标
聚合返回的游标只支持对已计算的游标进行操作的方法
https://docs.mongodb.com/v4.2/reference/method/db.collection.aggregate/#cursor-behavior
Cursors returned from aggregation only supports cursor methods that operate on evaluated cursors (i.e. cursors whose first batch has been retrieved)
cursor.hasNext()
cursor.next()
cursor.toArray()
cursor.forEach()
cursor.map()
cursor.objsLeftInBatch()
cursor.itcount()
cursor.pretty()
在 mongo shell 中,如果 aggregate()
方法返回的游标没有使用 var 关键字分配给一个变量,那么 mongo shell 将自动迭代游标20次
会话
getMore
getMore
db.collection.aggregate(pipeline, options)
pipeline
options
类型:Document
描述:aggregate() 方法传递给 aggregate 命令的额外选项,仅当pipeline为数组时可用
explain
allowDiskUse
类型:布尔
描述:是否允许使用临时文件
true
聚合操作可以将数据写入临时文件,位于 dbPath
目录中的子目录_tmp
但 $graphLookup、$addToSet、$push 阶段除外
cursor
maxTimeMS
db.killOp()
方法相同的机制终止超时的操作。mongodb 只在一个指定的中断点终止一个操作bypassDocumentValidation
aggregate()
方法绕过文档数据校验,允许管道处理阶段插入不满足数据校验的文档readConcern
类型:Document
描述
指定读取策略
格式
readConcern: { level : <value> }
collation
hint
comment
可通过 database profiler、currentOp、logs 追踪
writeConcern
文档
db.orders.insertMany([
{ _id: 1, cust_id: "abc1", ord_date: ISODate("2012-11-02T17:04:11.102Z"), status: "A", amount: 50 },
{ _id: 2, cust_id: "xyz1", ord_date: ISODate("2013-10-01T17:04:11.102Z"), status: "A", amount: 100 },
{ _id: 3, cust_id: "xyz1", ord_date: ISODate("2013-10-12T17:04:11.102Z"), status: "D", amount: 25 },
{ _id: 4, cust_id: "xyz1", ord_date: ISODate("2013-10-11T17:04:11.102Z"), status: "D", amount: 125 },
{ _id: 5, cust_id: "abc1", ord_date: ISODate("2013-11-12T17:04:11.102Z"), status: "A", amount: 25 }
])
group and sum
> var res = db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
])
> res
{ "_id" : "xyz1", "total" : 100 }
{ "_id" : "abc1", "total" : 75 }
显示聚合管道执行计划的详细信息
db.orders.explain().aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
])
使用外部存储处理大数据集
var results = db.stocks.aggregate(
[
{ $project : { cusip: 1, date: 1, price: 1, _id: 0 } },
{ $sort : { cusip : 1, date: 1 } }
],
{
allowDiskUse: true
}
)
指定聚合操作使用的索引
db.foodColl.createIndex( { qty: 1, type: 1 } );
db.foodColl.createIndex( { qty: 1, category: 1 } );
db.foodColl.aggregate(
[ { $sort: { qty: 1 }}, { $match: { category: "cake", qty: 10 } }, { $sort: { type: -1 } } ],
{ hint: { qty: 1, category: 1 } }
)
管道表达式
https://docs.mongodb.com/v4.2/core/aggregation-pipeline/#pipeline-expressions
https://docs.mongodb.com/v4.2/tutorial/update-documents-with-aggregation-pipeline/
https://docs.mongodb.com/v4.2/core/aggregation-pipeline-sharded-collections/#aggregation-pipeline-sharded-collection
聚合管道对值类型和结果大小有一些限制
结果大小限制
aggregate 命令
聚合命令可以返回游标,也可以将结果存储在集合中,结果集中的每个文档都受BSON文档大小限制,当前为 16MB,如果某个文档大小超过BSON大小限制,则聚合命令将产生错误
- 仅适用于结果集中返回的文档,管道中的文档不受此限制
- MongoDB 3.6删除了聚合命令以单个文档的形式返回结果的选项
aggregate() 方法
内存大小限制
聚合命令对单个集合进行操作,逻辑上将整个集合传递给聚合管道,为了优化操作,应尽可能比表面扫描整个集合
利用索引
mongodb的查询规划器(query planner)分析聚合管道,以确定是否可以使用索引来提高某些阶段的性能
预先过滤
当聚合操作只需针对集合中数据的一个子集,则在管道开头使用 $match、$limit、$skip 等阶段,限制输入文档的数量
在管道开头使用 $match 和 $sort 阶段,逻辑上相当于一个带有排序的查询,并且可以使用索引,如果可能,尽量在管道开头使用 $match 阶段
按指定字段对集合中的文档进行分组,每组输出一个文档,输出文档的_id
字段包含唯一值
类似 sql 中的 GROUP BY
输出文档中还可以添加自定义字段,用于显示累加器表达式值
如果文档不包含分组字段,则忽略该文档
区别于包含分组字段,但是值为null
格式
db.collection.aggregate([
{
$group:
{
_id: <expression>,
<field>: { <accumulator> : <expression> },
...
}
}
])
_id
"$<分组字段>"
或 操作符表达式)field
<accumulator>
描述:累加器(聚合)操作符
常用聚合操作符
操作符 | 描述 |
---|---|
$sum | 利用 $group 分组后,对同组内的文档,对指定字段的数值进行求和 |
$avg | 利用 $group 分组后,对同组内的文档,对指定字段的数值求平均值 |
$first | 利用 $group 分组后,对同组内的文档,显示指定字段的第一个值 |
$last | 利用 $group 分组后,对同组内的文档,显示指定字段的最后一个值 |
$max | 利用 $group 分组后,对同组内的文档,显示指定字段的最大值 |
$min | 利用 $group 分组后,对同组内的文档,显示指定字段的最小值 |
$push | 利用 $group 分组后,对同组内的文档,以数组的方式显示指定字段 |
$addToSet | 利用 $group 分组后,对同组内的文档,以数组的方式显示字段不重复的值 |
<expression>
"$<计算的字段>"
或 操作符表达式)示例
文档
db.sales.insertMany([
{ "_id" : 1, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("2"), "date" : ISODate("2014-03-01T08:00:00Z") },
{ "_id" : 2, "item" : "jkl", "price" : NumberDecimal("20"), "quantity" : NumberInt("1"), "date" : ISODate("2014-03-01T09:00:00Z") },
{ "_id" : 3, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt( "10"), "date" : ISODate("2014-03-15T09:00:00Z") },
{ "_id" : 4, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") },
{ "_id" : 5, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") },
{ "_id" : 6, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") },
{ "_id" : 7, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("10") , "date" : ISODate("2015-09-10T08:43:00Z") },
{ "_id" : 8, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") },
])
计算集合中文档的数量
db.sales.aggregate( [
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
] )
返回
{ "_id" : null, "count" : 8 }
_id
设为 null 或 常量
db.sales.aggregate( [
{
$group: {
_id: 404,
count: { $sum: 1 }
}
}
] )
返回
{ "_id" : 404, "count" : 8 }
检索某个字段的不同值
db.sales.aggregate( [ { $group : { _id : "$item" } } ] )
having
db.sales.aggregate(
[
// First Stage
{
$group :
{
_id : "$item",
totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }
}
},
// Second Stage
{
$match: { "totalSaleAmount": { $gte: 100 } }
}
]
)
多聚合操作符
db.sales.aggregate([
// First Stage
{
$match : { "date": { $gte: new ISODate("2014-01-01"), $lt: new ISODate("2015-01-01") } }
},
// Second Stage
{
$group : {
// 多个 key-value 键值对
_id : { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } },
averageQuantity: { $avg: "$quantity" },
count: { $sum: 1 }
}
},
// Third Stage
{
$sort : { totalSaleAmount: -1 }
}
])
显示并传递指定字段,包括文档中的现有字段或新计算的字段(新增)
对于嵌入式文档中的字段,可以通过 点表示法 或 嵌套字段表示法
"contact.address.country": <1 or 0 or expression>
contact: { address: { country: <1 or 0 or expression> } }
如果 $project 指定一个空文档则报错
格式
{ $project: { <specification(s)> } }
specifications
_id : <0 or false>
_id
字段<field> : <0 or 1>
1:包含指定字段
_id
字段默认显示和传递,其他字段默认不显示并排除0:排除指定字段
如果有条件的排除,需使用 REMOVE 变量
如果排除_id
以外的所有字段,则不能使用任何其他的规范表单
if you exclude fields, you cannot also specify the inclusion of fields, reset the value of existing fields, or add new fields.
除_id
字段外,其他字段默认不显示,但如果显式排除了某个字段,则其他所有字段将显示并传递
<field> : <expression>
常用操作符
示例
排除嵌入式文档中的指定字段
文档
db.books.insert({
"_id" : 1,
title: "abc123",
isbn: "0001122223334",
author: { last: "zzz", first: "aaa" },
copies: 5,
lastModified: "2016-07-28"
})
排除
db.books.aggregate( [ { $project : { "author.first" : 0, "lastModified" : 0 } } ] )
db.bookmarks.aggregate( [ { $project: { "author": { "first": 0}, "lastModified" : 0 } } ] )
包含嵌入式文档中的指定字段
文档
db.bookmarks.insertMany([
{ _id: 1, user: "1234", stop: { title: "book1", author: "xyz", page: 32 } },
{ _id: 2, user: "7890", stop: [ { title: "book2", author: "abc", page: 5 }, { title: "book3", author: "ijk", page: 100 } ] }
])
包含
db.bookmarks.aggregate( [ { $project: { "stop.title": 1 } } ] )
db.bookmarks.aggregate( [ { $project: { stop: { title: 1 } } } ] )
结果
{ "_id" : 1, "stop" : { "title" : "book1" } }
{ "_id" : 2, "stop" : [ { "title" : "book2" }, { "title" : "book3" } ] }
多个值将自动以数组的形式返回
新增字段
db.books.aggregate(
[
{
$project: {
title: 1,
isbn: {
prefix: { $substr: [ "$isbn", 0, 3 ] },
group: { $substr: [ "$isbn", 3, 2 ] }
},
lastName: "$author.last",
copiesSold: "$copies"
}
}
]
)
结果
{
"_id" : 1,
"title" : "abc123",
"isbn" : {
"prefix" : "000",
"group" : "11"
},
"lastName" : "zzz",
"copiesSold" : 5
}
新增数组
文档
{ "_id" : ObjectId("55ad167f320c6be244eb3b95"), "x" : 1, "y" : 1 }
新增
db.collection.aggregate( [ { $project: { myArray: [ "$x", "$y", "$someField" ] } } ] )
结果
{ "_id" : ObjectId("55ad167f320c6be244eb3b95"), "myArray" : [ 1, 1, null ] }
如果数组中引用了不存在的字段,则用 null 代替
格式
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
优化
当 $sort 在 $limit 之前且中间没有改变文档数量的操作时,优化器可以将 $limit 合并到 $sort 中,即 $sort 操作在进行过程中只维护顶部的 n 个结果(n 为 $limit 限定的值),mongodb 只在内存中存储 n 个文档
$sort 阶段只能占用 100 MB 的内存,默认超过则报错。为了处理大型数据集,设置 allowDiskUse 为 true,允许 $sort 操作将数据写入临时文件
如果管道前面没有 $project、$unwind、$group 阶段,则$sort 可利用索引进行排序
格式
{ $skip: <positive integer> }
格式
{ $limit: <positive integer> }
find()
、findOne()
那样利用索引格式
{ $match: { <query> } }
限制
$match 查询语法与读取操作查询语法相同;例如:$match不接受原始聚合表达式。要在$match中包含聚合表达式,请使用$expr查询表达式
{ $match: { $expr: { <aggregation expression> } } }
要在 $match 中使用 $text,则必须作为管道的第一阶段
示例
相等匹配
db.articles.aggregate(
[ { $match : { author : "dave" } } ]
);
条件查询
db.articles.aggregate( [
{ $match: { $or: [ { score: { $gt: 70, $lt: 90 } }, { views: { $gte: 1000 } } ] } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
$match 选择分数大于70小于90的文档,或者视图大于等于1000的文档,这些文档通过管道传输给 $group 阶段
$group 统计文档数量
结果
{ "_id" : null, "count" : 5 }
对同一数据库中未分片的集合进行左外连接,用于查找当前集合中与另一集合条件匹配的文档
相当于关系数据库中的左外联查询
左外联:返回包括左表中的所有记录和右表中符合查询条件的记录
右外联:返回包括右表中的所有记录和左表中符合查询条件的记录
https://blog.csdn.net/plg17/article/details/78758593
{
$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
示例
localField为单一值
文档
db.orders.insert([
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
{ "_id" : 3 }
])
db.inventory.insert([
{ "_id" : 1, "sku" : "almonds", description: "product 1", "instock" : 120 },
{ "_id" : 2, "sku" : "bread", description: "product 2", "instock" : 80 },
{ "_id" : 3, "sku" : "cashews", description: "product 3", "instock" : 60 },
{ "_id" : 4, "sku" : "pecans", description: "product 4", "instock" : 70 },
{ "_id" : 5, "sku": null, description: "Incomplete" },
{ "_id" : 6 }
])
通过 orders 集合中的 item 字段和 inventory 集合汇总的 sku 字段,将两个集合连接起来
db.orders.aggregate([
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
])
查询 orders 集合中 item 字段的值与 inventory 集合中 sku 字段的值 相等的 文档
输出文档中的 inventory_docs 字段包含 inventory 集合中符合条件的文档
结果
{
"_id" : 1,
"item" : "almonds",
"price" : 12,
"quantity" : 2,
"inventory_docs" : [
{ "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
]
}
{
"_id" : 2,
"item" : "pecans",
"price" : 20,
"quantity" : 1,
"inventory_docs" : [
{ "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
]
}
{
"_id" : 3,
"inventory_docs" : [
{ "_id" : 5, "sku" : null, "description" : "Incomplete" },
{ "_id" : 6 }
]
}
orders 集合中的
{ "_id" : 3 }
文档不包含 item 字段,则$lookup将其视为 null 匹配 inventory 集合中的{ "_id" : 5, "sku" : null, "description" : "Incomplete" }
和{ "_id" : 6 }
两个文档
localFIeld 为数组
文档
db.classes.insert( [
{ _id: 1, title: "Reading is ...", enrollmentlist: [ "giraffe2", "pandabear", "artie" ], days: ["M", "W", "F"] },
{ _id: 2, title: "But Writing ...", enrollmentlist: [ "giraffe1", "artie" ], days: ["T", "F"] }
])
db.members.insert( [
{ _id: 1, name: "artie", joined: new Date("2016-05-01"), status: "A" },
{ _id: 2, name: "giraffe", joined: new Date("2017-05-01"), status: "D" },
{ _id: 3, name: "giraffe1", joined: new Date("2017-10-01"), status: "A" },
{ _id: 4, name: "panda", joined: new Date("2018-10-11"), status: "A" },
{ _id: 5, name: "pandabear", joined: new Date("2018-12-01"), status: "A" },
{ _id: 6, name: "giraffe2", joined: new Date("2018-12-01"), status: "D" }
])
通过 classes 集合中的 enrollmentlist 字段 和 members 集合中的 name 字段,将两个集合连接起来
db.classes.aggregate([
{
$lookup:
{
from: "members",
localField: "enrollmentlist",
foreignField: "name",
as: "enrollee_info"
}
}
])
查询 classes 集合中 enrollmentlist 数组中的元素跟 members 集合中 name 字段值相等的文档
输出文档中的 enrollee_info 字段包含 members 集合中符合条件的文档
结果
{
"_id" : 1,
"title" : "Reading is ...",
"enrollmentlist" : [ "giraffe2", "pandabear", "artie" ],
"days" : [ "M", "W", "F" ],
"enrollee_info" : [
{ "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
{ "_id" : 5, "name" : "pandabear", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "A" },
{ "_id" : 6, "name" : "giraffe2", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "D" }
]
}
{
"_id" : 2,
"title" : "But Writing ...",
"enrollmentlist" : [ "giraffe1", "artie" ],
"days" : [ "T", "F" ],
"enrollee_info" : [
{ "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
{ "_id" : 3, "name" : "giraffe1", "joined" : ISODate("2017-10-01T00:00:00Z"), "status" : "A" }
]
}
{
$lookup:
{
from: <collection to join>,
let: { <var_1>: <expression>, …, <var_n>: <expression> },
pipeline: [ <pipeline to execute on the collection to join> ],
as: <output array field>
}
}
from
left
let : { <引用变量名> : "$<输入文档中的字段>" }
pipeline
类型:数组
指定要在 from 集合上运行的管道,用以筛选符合条件的文档
如要返回所有的文档,则指定一个不含任何阶段的空管道
[ ]
pipeline 管道中不能包含 $out 和 $merge 阶段
pipeline 管道阶段 能 直接访问 from 集合中的文档字段,通过"$<from集合中的文档字段>"
pipeline 管道阶段 不能 直接访问输入文档中的字段,必须首先在 let 子句中定义中间变量,然后才能在 pipeline 管道的各个阶段中引用
$$<variable>
的形式引用变量as
示例
多条件查询
文档
db.orders.insert([
{ "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1 },
{ "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60 }
])
db.warehouses.insert([
{ "_id" : 1, "stock_item" : "almonds", warehouse: "A", "instock" : 120 },
{ "_id" : 2, "stock_item" : "pecans", warehouse: "A", "instock" : 80 },
{ "_id" : 3, "stock_item" : "almonds", warehouse: "B", "instock" : 60 },
{ "_id" : 4, "stock_item" : "cookies", warehouse: "B", "instock" : 40 },
{ "_id" : 5, "stock_item" : "cookies", warehouse: "A", "instock" : 80 }
])
通过 item字段 以及 条件(库存数量是否满足订单数量),将orders集合和warehouses集合连接起来
db.orders.aggregate([
{
$lookup:
{
from: "warehouses",
let: { order_item: "$item", order_qty: "$ordered" },
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$stock_item", "$$order_item" ] },
{ $gte: [ "$instock", "$$order_qty" ] }
]
}
}
},
{ $project: { stock_item: 0, _id: 0 } }
],
as: "stockdata"
}
}
])
根据 orders 集合中的 name 字段,查询 warehouses 集合中库存数量满足订单数量的文档
输出文档中的 stockdata 字段包含符合条件的 warehouses 集合中的文档
结果
{ "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2,
"stockdata" : [ { "warehouse" : "A", "instock" : 120 }, { "warehouse" : "B", "instock" : 60 } ] }
{ "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1,
"stockdata" : [ { "warehouse" : "A", "instock" : 80 } ] }
{ "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60,
"stockdata" : [ { "warehouse" : "A", "instock" : 80 } ] }
不相关子查询
子查询或内部查询
- 嵌套在其它查询中的查询
主查询或外部查询
- 包含子查询的查询
不相关子查询
- 内部查询的执行独立于外部查询,内部查询只执行一次,然后将结果作为外部查询的条件
相关子查询
- 内部查询的执行依赖于外部查询的数据,外部查询每执行一次,内部查询也会执行一次。
- 每次都是外部查询先执行,将当前查询数据传递给内部查询,然后执行内部查询,根据内部查询的执行结果判断当前数据是否满足外部查询的where条件,若满足则当前数据是符合要求的记录
- 外部查询依次扫描每条记录,重复执行上述过程
https://blog.csdn.net/qiushisoftware/article/details/80874463
格式
{ $count: <string> }
$
开头,不能包含点.
字符示例
$count 行为等价于 $group + $project
db.collection.aggregate( [
{ $group: { _id: null, myCount: { $sum: 1 } } },
{ $project: { _id: 0 } }
] )
db.collection.aggregate([
{
$count:"myCount"
}
])
格式
{ $unwind: <field path> }
{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
path
includeArrayIndex
$
开头preserveNullAndEmptyArrays
preserve 保留
类型:布尔
描述:如果文档不包含 path 指定的字段,或字段值为null,或字段值为空数组[ ]
true
$unwind 原样输出该文档
false【默认】
$unwind 不输出该文档
示例
preserveNullAndEmptyArrays 默认false
db.inventory2.insertMany([
{ "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] },
{ "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] },
{ "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" },
{ "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") },
{ "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null }
])
展开
db.inventory2.aggregate( [ { $unwind: "$sizes" } ] )
db.inventory2.aggregate( [ { $unwind: { path: "$sizes" } } ] )
两种语法效果一样
结果
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
记录索引,输出文档
db.inventory2.aggregate( [
{
$unwind:
{
path: "$sizes",
includeArrayIndex: "arrayIndex",
preserveNullAndEmptyArrays: true
}
}])
结果
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
{ "_id" : 2, "item" : "EFG", "price" : NumberDecimal("120") }
{ "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
{ "_id" : 4, "item" : "LMN", "price" : NumberDecimal("10") }
{ "_id" : 5, "item" : "XYZ", "price" : NumberDecimal("5.75"), "sizes" : null }
解析嵌套数组
{
_id: "1",
"items" : [
{
"name" : "pens",
"tags" : [ "writing", "office", "school", "stationary" ],
"price" : NumberDecimal("12.00"),
"quantity" : NumberInt("5")
},
{
"name" : "envelopes",
"tags" : [ "stationary", "office" ],
"price" : NumberDecimal("1.95"),
"quantity" : NumberInt("8")
}
]
}
db.sales.aggregate([
// First Stage
{ $unwind: "$items" },
// Second Stage
{ $unwind: "$items.tags" }
])
?
将聚合操作的结果写入指定的集合
$out 必须是管道的最后一个阶段
不能写入固定集合中
如果指定的集合不存在,则在完成聚合操作后,会创建该集合
在聚合操作完成之前,该集合是不可见的。如果聚合操作失败,则不会创建
如果指定的集合已经存在,则在完成聚合操作后,会用聚合操作结果覆盖原有数据
$out 操作符不会改变原集合上建立的索引,如果聚合操作的结果文档违反任一唯一索引(包括原集合_id
字段上建立的索引),则写入失败
格式
{ $out: "<output-collection>" }
示例
db.books.insert([
{ "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 },
{ "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 },
{ "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 },
{ "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 },
{ "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 }
])
聚合
db.books.aggregate( [
{ $group : { _id : "$author", books: { $push: "$title" } } },
{ $out : "authors" }
] )
结果在当前数据库中新增 authors 集合
{ "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] }
{ "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }
格式
{ $addFields: { <newField>: <expression>, ... } }
格式
{ $push: <expression> }
格式
{ $addToSet: <expression> }
"$<field>"
,则以数组的形式返回该字段不重复的值利用 $group 分组后,对同组内的文档,对指定字段的数值进行求和
{$sum:1}
表示计算文档数量总和,管道中的每个文档代表数值1
在 $group 阶段,分组过程中,计算每组文档的数量
3.2 版本及之前,仅用于 $group 阶段
格式
$group 阶段
{ $sum: <expression> }
其他阶段
{ $sum: <expression> }
{ $sum: [ <expression1>, <expression2> ... ] }
非数字或不存在字段
Example | Field Values | Results |
---|---|---|
{ $sum : <field> } |
Numeric |
Sum of Values |
{ $sum : <field> } |
Numeric and Non-Numeric |
Sum of Numeric Values |
{ $sum : <field> } |
Non-Numeric or Non-Existent |
0 |
数组字段
利用 $group 分组后,对同组内的文档,对指定字段的数值求平均值
3.2 版本及之前,仅用于 $group 阶段
格式
$group 阶段
{ $avg: <expression> }
其他阶段
{ $avg: <expression> }
{ $avg: [ <expression1>, <expression2> ... ] }
利用 $group 分组后,对同组内的文档,返回 指定字段 的第一个值
一般在文档排序后使用才有意义
当在 $group 阶段中使用$first时,$group 阶段应该在 $sort 阶段之后,以使输入文档按照已定义的顺序
尽管 $sort 阶段将有序的文档作为输入传递到 $group 阶段,但 $group 不能保证在其自己的输出中维护这种排序顺序。
只能应用于 $group 阶段
格式
{ $first: <expression> }
利用 $group 分组后,对同组内的文档,返回 指定字段 的最后一个值
一般在文档排序后使用才有意义
当在 $group 阶段中使用$first时,$group 阶段应该在 $sort 阶段之后,以使输入文档按照已定义的顺序
只能应用于 $group 阶段
格式
{ $last: <expression> }
格式
$group 阶段
{ $max: <expression> }
其他阶段
{ $max: <expression> }
{ $max: [ <expression1>, <expression2> ... ] }
格式
$group 阶段
{ $min: <expression> }
其他阶段
{ $min: <expression> }
{ $min: [ <expression1>, <expression2> ... ] }
格式
$switch: {
branches: [
{ case: <expression>, then: <expression> },
{ case: <expression>, then: <expression> },
...
],
default: <expression>
}
branches
类型:文档数组
描述:
控制分支,每个分支都必须具有 case 和 then 字段
case
值为可以解析为布尔值的任何有效的 表达式,如果不是则强制转换为布尔值
then
值为任何有效的表达式
必须至少包含一个条件分支
default【可选】
示例
文档
db.grades.insert([
{ "_id" : 1, "name" : "Susan Wilkes", "scores" : [ 87, 86, 78 ] },
{ "_id" : 2, "name" : "Bob Hanna", "scores" : [ 71, 64, 81 ] },
{ "_id" : 3, "name" : "James Torrelio", "scores" : [ 91, 84, 97 ] }
])
聚合
db.grades.aggregate( [
{
$project:
{
"name" : 1,
"summary" :
{
$switch:
{
branches: [
{
case: { $gte : [ { $avg : "$scores" }, 90 ] },
then: "Doing great!"
},
{
case: { $and : [ { $gte : [ { $avg : "$scores" }, 80 ] },
{ $lt : [ { $avg : "$scores" }, 90 ] } ] },
then: "Doing pretty well."
},
{
case: { $lt : [ { $avg : "$scores" }, 80 ] },
then: "Needs improvement."
}
],
default: "No scores found."
}
}
}
}
] )
结果
{ "_id" : 1, "name" : "Susan Wilkes", "summary" : "Doing pretty well." }
{ "_id" : 2, "name" : "Bob Hanna", "summary" : "Needs improvement." }
{ "_id" : 3, "name" : "James Torrelio", "summary" : "Doing great!" }
格式
{ $size: <expression> }
expression
可以解析为数组的任何有效的表达式
3.4 版本中被废弃,现在是 substrBytes 的别名
格式
{ $substr: [ <string>, <start>, <length> ] }
$<字段名>
查询并返回子字符串在字段中第一次出现位置的索引,如果没有找到则返回 -1
UTF-8 字节索引
格式
{ $indexOfBytes: [ <string expression>, <substring expression>, <start>, <end> ] }
示例
db.inventory.insert([
{ "_id" : 1, "item" : "foo" },
{ "_id" : 2, "item" : "fóofoo" },
{ "_id" : 3, "item" : "the foo bar" },
{ "_id" : 4, "item" : "hello world fóo" },
{ "_id" : 5, "item" : null },
{ "_id" : 6, "amount" : 3 }
])
聚合
db.inventory.aggregate(
[
{
$project:
{
byteLocation: { $indexOfBytes: [ "$item", "foo" ] },
}
}
]
)
结果
{ "_id" : 1, "byteLocation" : "0" }
{ "_id" : 2, "byteLocation" : "4" }
{ "_id" : 3, "byteLocation" : "4" }
{ "_id" : 4, "byteLocation" : "-1" }
{ "_id" : 5, "byteLocation" : null }
{ "_id" : 6, "byteLocation" : null }
注意 fóofoo 索引是4
é
is encoded using two bytes.
每个文档都有一个返回结果
格式
{ $strLenBytes: <string expression> }
示例
文档
{ "_id" : 1, "name" : "apple" }
{ "_id" : 2, "name" : "banana" }
{ "_id" : 3, "name" : "éclair" }
{ "_id" : 4, "name" : "hamburger" }
{ "_id" : 5, "name" : "jalape?o" }
{ "_id" : 6, "name" : "pizza" }
{ "_id" : 7, "name" : "tacos" }
{ "_id" : 8, "name" : "寿司" }
聚合
db.food.aggregate(
[
{
$project: {
"name": 1,
"length": { $strLenBytes: "$name" }
}
}
]
)
结果
{ "_id" : 1, "name" : "apple", "length" : 5 }
{ "_id" : 2, "name" : "banana", "length" : 6 }
{ "_id" : 3, "name" : "éclair", "length" : 7 }
{ "_id" : 4, "name" : "hamburger", "length" : 9 }
{ "_id" : 5, "name" : "jalape?o", "length" : 9 }
{ "_id" : 6, "name" : "pizza", "length" : 5 }
{ "_id" : 7, "name" : "tacos", "length" : 5 }
{ "_id" : 8, "name" : "寿司", "length" : 6 }
对两个字符串执行不区分大小写的比较
仅适用于 ASCII