定义
$fill5.3 版本中的新增功能。
填充文档中的
null和缺失的字段值。您可以使用
$fill来填充缺失的数据点:根据周围的数值排序。
具备固定值。
语法
$fill 阶段采用以下语法:
{ $fill: { partitionBy: <expression>, partitionByFields: [ <field 1>, <field 2>, ... , <field n> ], sortBy: { <sort field 1>: <sort order>, <sort field 2>: <sort order>, ..., <sort field n>: <sort order> }, output: { <field 1>: { value: <expression> }, <field 2>: { method: <string> }, ... } } }
$fill 阶段采用包含以下字段的文档:
字段 | 必要性 | 说明 |
|---|---|---|
Optional | 指定用于对文档进行群组的表达式。在 如果省略 partitionBy 和 partitionByFields, partitionBy 和 partitionByFields 是互斥的。 | |
Optional | 指定一个字段大量作为复合键,对文档群组。 在 如果省略 partitionBy 和 partitionByFields, partitionBy 和 partitionByFields 是互斥的。 | |
指定每个分区内用于对文档进行排序的字段。使用与 | ||
必需 | ||
必需 |
行为和限制
partitionByFields 限制
如果 partitionByFields 数组中有任何字段名称,则 $fill 会返回错误:
求值为非字符串值。
以
$开头。
linear 行为
linear 填充方法根据序列中周围的非 null 值,使用线性插值填充 null 和缺失字段。
对于字段为
null或缺失的每个文档,linearFill会根据 sortBy 顺序,按照周围非null值之间的缺失值范围比例填充这些字段。为了确定缺失字段的值,linearFill使用:周围非
null值之差。周围值之间要填充的
null字段的数量。
如果根据 sortBy 排序,
linear方法可以填充多个连续的null值,前提是这些值的前后都是非null值。例子
如果集合中包含这些文档:
{ index: 0, value: 0 }, { index: 1, value: null }, { index: 2, value: null }, { index: 3, value: null }, { index: 4, value: 10 } 使用
linear填充方法填充null值后,文档变为:{ index: 0, value: 0 }, { index: 1, value: 2.5 }, { index: 2, value: 5 }, { index: 3, value: 7.5 }, { index: 4, value: 10 } null前后没有非null值的值仍为null。如需使用
linear填充方法,还必须使用 sortBy 字段对数据排序。
有关使用 linear 填充方法的完整示例,请参阅使用线性插值填充缺失字段值。
locf 行为
locf 代表最后的观察结果。
如果填写的字段包含
null和非空值,locf会根据 sortBy 排序将null和缺失值设置为该字段的最后一个已知非空值。如果该字段在分区中仅包含
null或缺失值,locf会将该分区的字段值设置为null。null而排序顺序中出现在非空值之前的缺失字段值仍为null。
如需使用
locf填充方法,还必须使用 sortBy 字段对数据排序。
有关使用 locf 填充方法的完整示例,请参阅根据上一次观察到的值填充缺失字段值。
$fill和聚合操作符的比较
要填充文档中的 null 和缺失的字段值,您可以使用:
$fill阶段。使用
$fill阶段时,输出中指定的字段与源数据使用的字段相同。$linearFill和$locf聚合操作符。当您
$linearFill或$locf时,可以为不同于作为源数据使用的字段设置值。
示例
本节的示例展示了如何使用 $fill 填充缺失值:
用常量值填充缺失字段值
一家鞋店建立了 dailySales 集合,其中包含总结每天销售情况的文档。这家鞋店销售以下类型的鞋:
bootssandalssneakers
创建以下 dailySales 集合:
db.dailySales.insertMany( [ { "date": ISODate("2022-02-02"), "bootsSold": 10, "sandalsSold": 20, "sneakersSold": 12 }, { "date": ISODate("2022-02-03"), "bootsSold": 7, "sneakersSold": 18 }, { "date": ISODate("2022-02-04"), "sneakersSold": 5 } ] )
并非 dailySales 集合中的所有文档都包含每种鞋子类型。如果某一类型的鞋子缺失,则表示在相应日期没有售出该类型的鞋子。
以下示例使用 $fill 将每天销售中缺少的鞋款的销售数量设置为 0:
db.dailySales.aggregate( [ { $fill: { output: { "bootsSold": { value: 0 }, "sandalsSold": { value: 0 }, "sneakersSold": { value: 0 } } } } ] )
在前面的管道中:
示例输出:
[ { _id: ObjectId("6202df9f394d47411658b51e"), date: ISODate("2022-02-02T00:00:00.000Z"), bootsSold: 10, sandalsSold: 20, sneakersSold: 12 }, { _id: ObjectId("6202df9f394d47411658b51f"), date: ISODate("2022-02-03T00:00:00.000Z"), bootsSold: 7, sneakersSold: 18, sandalsSold: 0 }, { _id: ObjectId("6202df9f394d47411658b520"), date: ISODate("2022-02-04T00:00:00.000Z"), sneakersSold: 5, bootsSold: 0, sandalsSold: 0 } ]
用线性插值填充缺失字段值
创建一个包含以下文档的 stock集合,该集合每小时追踪一家公司的股票价格:
db.stock.insertMany( [ { time: ISODate("2021-03-08T09:00:00.000Z"), price: 500 }, { time: ISODate("2021-03-08T10:00:00.000Z"), }, { time: ISODate("2021-03-08T11:00:00.000Z"), price: 515 }, { time: ISODate("2021-03-08T12:00:00.000Z") }, { time: ISODate("2021-03-08T13:00:00.000Z") }, { time: ISODate("2021-03-08T14:00:00.000Z"), price: 485 } ] )
集合中的某些文档缺少 price 字段。
要使用线性插值填充缺失的 price 值,请使用$fill和 linear 填充方法:
db.stock.aggregate( [ { $fill: { sortBy: { time: 1 }, output: { "price": { method: "linear" } } } } ] )
在前面的管道中:
示例输出:
[ { _id: ObjectId("620ad41c394d47411658b5e9"), time: ISODate("2021-03-08T09:00:00.000Z"), price: 500 }, { _id: ObjectId("620ad41c394d47411658b5ea"), time: ISODate("2021-03-08T10:00:00.000Z"), price: 507.5 }, { _id: ObjectId("620ad41c394d47411658b5eb"), time: ISODate("2021-03-08T11:00:00.000Z"), price: 515 }, { _id: ObjectId("620ad41c394d47411658b5ec"), time: ISODate("2021-03-08T12:00:00.000Z"), price: 505 }, { _id: ObjectId("620ad41c394d47411658b5ed"), time: ISODate("2021-03-08T13:00:00.000Z"), price: 495 }, { _id: ObjectId("620ad41c394d47411658b5ee"), time: ISODate("2021-03-08T14:00:00.000Z"), price: 485 } ]
根据上一次观察到的值填充缺失字段值
创建包含以下文档的 restaurantReviews集合,其中存储单个餐厅在一段时间内的查看分数:
db.restaurantReviews.insertMany( [ { date: ISODate("2021-03-08"), score: 90 }, { date: ISODate("2021-03-09"), score: 92 }, { date: ISODate("2021-03-10") }, { date: ISODate("2021-03-11") }, { date: ISODate("2021-03-12"), score: 85 }, { date: ISODate("2021-03-13") } ] )
集合中的某些文档缺少 score 字段。
如需填充缺失的 score 字段并确保数据中没有空白,请使用 $fill。在以下示例中,$fill 使用 locf 填充方法,用序列中的前一个 score 值来填充缺失的 score 值:
db.restaurantReviews.aggregate( [ { $fill: { sortBy: { date: 1 }, output: { "score": { method: "locf" } } } } ] )
在前面的管道中:
$fill填充缺失的score值。sortBy: { date: 1 }按date字段以升序排列文档,从最早到最晚。输出指定:
score作为要填写缺失值的字段。{ method: "locf" }作为填充方法。locf填充- 方法使用序列中最后观察到的
score值填充缺失的score值。
示例输出:
[ { _id: ObjectId("62040bc9394d47411658b553"), date: ISODate("2021-03-08T00:00:00.000Z"), score: 90 }, { _id: ObjectId("62040bc9394d47411658b554"), date: ISODate("2021-03-09T00:00:00.000Z"), score: 92 }, { _id: ObjectId("62040bc9394d47411658b555"), date: ISODate("2021-03-10T00:00:00.000Z"), score: 92 }, { _id: ObjectId("62040bc9394d47411658b556"), date: ISODate("2021-03-11T00:00:00.000Z"), score: 92 }, { _id: ObjectId("62040bc9394d47411658b557"), date: ISODate("2021-03-12T00:00:00.000Z"), score: 85 }, { _id: ObjectId("62040bc9394d47411658b558"), date: ISODate("2021-03-13T00:00:00.000Z"), score: 85 } ]
为不同分区填充数据
继续以前面的餐厅评论为例,但该集合现在追踪的不是一家餐厅,而是多家餐厅的评论。
创建名为 restaurantReviewsMultiple 的集合,然后用这些文档填充该集合:
db.restaurantReviewsMultiple.insertMany( [ { date: ISODate("2021-03-08"), restaurant: "Joe's Pizza", score: 90 }, { date: ISODate("2021-03-08"), restaurant: "Sally's Deli", score: 75 }, { date: ISODate("2021-03-09"), restaurant: "Joe's Pizza", score: 92 }, { date: ISODate("2021-03-09"), restaurant: "Sally's Deli" }, { date: ISODate("2021-03-10"), restaurant: "Joe's Pizza" }, { date: ISODate("2021-03-10"), restaurant: "Sally's Deli", score: 68 }, { date: ISODate("2021-03-11"), restaurant: "Joe's Pizza", score: 93 }, { date: ISODate("2021-03-11"), restaurant: "Sally's Deli" } ] )
集合中的某些文档缺少 score 字段。
如需填充缺失的 score 字段并确保数据中没有空白,请使用 $fill。在以下示例中,$fill 使用 locf 填充方法,用序列中的前一个 score 值来填充缺失的 score 值:
db.restaurantReviewsMultiple.aggregate( [ { $fill: { sortBy: { date: 1 }, partitionBy: { "restaurant": "$restaurant" }, output: { "score": { method: "locf" } } } } ] )
在前面的管道中:
$fill填充缺失的score值。sortBy: { date: 1 }按date字段以升序排列文档,从最早到最晚。partitionBy: { "restaurant": "$restaurant" }按restaurant对数据进行分区。有两家餐厅:Joe's Pizza和Sally's Deli。输出指定:
score作为要填写缺失值的字段。{ method: "locf" }作为填充方法。locf填充- 方法使用序列中最后观察到的
score值填充缺失的score值。
示例输出:
[ { _id: ObjectId("620559f4394d47411658b58f"), date: ISODate("2021-03-08T00:00:00.000Z"), restaurant: "Joe's Pizza", score: 90 }, { _id: ObjectId("620559f4394d47411658b591"), date: ISODate("2021-03-09T00:00:00.000Z"), restaurant: "Joe's Pizza", score: 92 }, { _id: ObjectId("620559f4394d47411658b593"), date: ISODate("2021-03-10T00:00:00.000Z"), restaurant: "Joe's Pizza", score: 92 }, { _id: ObjectId("620559f4394d47411658b595"), date: ISODate("2021-03-11T00:00:00.000Z"), restaurant: "Joe's Pizza", score: 93 }, { _id: ObjectId("620559f4394d47411658b590"), date: ISODate("2021-03-08T00:00:00.000Z"), restaurant: "Sally's Deli", score: 75 }, { _id: ObjectId("620559f4394d47411658b592"), date: ISODate("2021-03-09T00:00:00.000Z"), restaurant: "Sally's Deli", score: 75 }, { _id: ObjectId("620559f4394d47411658b594"), date: ISODate("2021-03-10T00:00:00.000Z"), restaurant: "Sally's Deli", score: 68 }, { _id: ObjectId("620559f4394d47411658b596"), date: ISODate("2021-03-11T00:00:00.000Z"), restaurant: "Sally's Deli", score: 68 } ]
指明填充字段的方式是否为 $fill
填充缺失值时,输出结果不会显示该值是用 $fill 操作符填充的,还是该值最初就存在于文档中。为了区分已填充的值和已存在的值,可以在 $fill 前使用 $set 阶段,并根据值是否存在设置新字段。
示例,创建一个包含以下文档的 restaurantReviews集合,其中存储一家餐厅在一段时间内的查看分数:
db.restaurantReviews.insertMany( [ { date: ISODate("2021-03-08"), score: 90 }, { date: ISODate("2021-03-09"), score: 92 }, { date: ISODate("2021-03-10") }, { date: ISODate("2021-03-11") }, { date: ISODate("2021-03-12"), score: 85 }, { date: ISODate("2021-03-13") } ] )
集合中的某些文档缺少 score字段。您可以使用 $fill操作符填充缺失的 score 值。
创建管道来执行以下操作:
使用
$set向每个文档添加一个新字段,指示文档的score字段在$fill操作符填充值之前是否存在。这个新字段名为valueExisted。使用序列中最后观察到的
score填充缺失的score值。填充方法locf代表“最后一次观测结转”。
该管道类似于以下代码:
db.restaurantReviews.aggregate( [ { $set: { "valueExisted": { "$ifNull": [ { "$toBool": { "$toString": "$score" } }, false ] } } }, { $fill: { sortBy: { date: 1 }, output: { "score": { method: "locf" } } } } ] )
注意
处理零值
输出:
[ { _id: ObjectId("63595116b1fac2ee2e957f15"), date: ISODate("2021-03-08T00:00:00.000Z"), score: 90, valueExisted: true }, { _id: ObjectId("63595116b1fac2ee2e957f16"), date: ISODate("2021-03-09T00:00:00.000Z"), score: 92, valueExisted: true }, { _id: ObjectId("63595116b1fac2ee2e957f17"), date: ISODate("2021-03-10T00:00:00.000Z"), valueExisted: false, score: 92 }, { _id: ObjectId("63595116b1fac2ee2e957f18"), date: ISODate("2021-03-11T00:00:00.000Z"), valueExisted: false, score: 92 }, { _id: ObjectId("63595116b1fac2ee2e957f19"), date: ISODate("2021-03-12T00:00:00.000Z"), score: 85, valueExisted: true }, { _id: ObjectId("63595116b1fac2ee2e957f1a"), date: ISODate("2021-03-13T00:00:00.000Z"), valueExisted: false, score: 85 } ]
本页中的 Node.js 示例使用来自 Atlas 示例数据集的 sample_weatherdata.data 集合。要学习如何创建免费的 MongoDB Atlas 集群并加载示例数据集,请参阅 MongoDB Node.js 驱动程序文档中的入门指南。
要使用MongoDB Node.js驱动程序将 $fill 阶段添加到聚合管道,请在管道对象中使用 $fill操作符。
以下示例创建了一个填充空值或缺失值的管道。该管道包括以下阶段:
$group阶段按ts字段对输入文档进行分组,并计算每组的seaSurfaceTemperature.value平均值。$fill阶段按_id字段对分组数据进行升序排序,并使用线性插值填充空值或缺失的seaSurfaceTemperature值。
const pipeline = [ { $group: { _id: "$ts", seaSurfaceTemperature: { $avg: "$seaSurfaceTemperature.value" }, } }, { $fill: { sortBy: { _id: 1 }, output: { seaSurfaceTemperature: { method: "linear" } } } } ]; const cursor = collection.aggregate(pipeline); return cursor;