$densify
阶段可以为文档序列中字段缺失的某些值创建新文档。其主要的用途有:
- 补齐时间序列数据。
- 为分组数据添加缺失值。
- 为指定的值范围填充数据。
语法
$densify
阶段的语法:
{
$densify: {
field: <fieldName>,
partitionByFields: [ <field 1>, <field 2> ... <field n> ],
range: {
step: <number>,
unit: <time unit>,
bounds: < "full" || "partition" > || [ < lower bound >, < upper bound > ]
}
}
}
$densify
阶段的参数主要有以下字段:
field
必选字段,要填充的字段,字段的值必须是数值或日期类型,文档中其他没有被field
指定的字段则会直接通过管道,不会被修改。如果指定的<field>
在内嵌文档或数组中,需要使用点号。
partitionByFields
可选字段,一组字段构成的键值用于对文档进行分组。在$densify
阶段,每组文档被称为一个分区。若省略这个字段,$densify
则对整个集合使用一个分组。
range
必选字段,一个对象,指定数据如何被填充。
range.bounds
必选字段,可以用下面两种方式指定range.bounds
:
- 数组形式:
[ < lower bound >, < upper bound > ],
- 字符串形式:
"full"
或"partition"
如果bounds
是一个数组:
$densify
添加涵盖指定范围值的文档。bounds
的数据类型必须与填充的字段类型保持一致。
如果bounds
为"full"
:
$densify
添加涵盖指定范围值的文档。
如果bounds
为"partition"
:
$densify
将文档添加到每个分区,类似于单独对每个分区单独运行full
范围致密化。
range.step
必须字段,所有文档中field
值的增量,$densify
按照step(步长)
在已经存在的文档之间创建新文档。如果指定了range.unit
字段,step
必须是一个整数,否则step
可以是任意的数值。
range.unit
如果field
为日期则是必须字段,unit
用于日期类型的field
增量的单位。可以指定为下面字符串的值之一:
- millisecond
- second
- minute
- hour
- day
- week
- month
- quarter
- year
使用
field
的限制
如果文档包含的field
有下列情况将报错:
- 集合中任何文档中指定的
field
值有日期类型,且没有指定unit
字段的 - 集合中任何文档中指定的
field
值有数值类型,且没有指定unit
字段的 - 如果要
densify
的字段field
名称以$
开头,必须要对其改名,可以使用$project
partitionByFields的限制
下面的情况出现在partitionByFields
数组字段名中时将报错:
- 求值结果为非字符串值
- 以
$
开头
range.bounds的行为
如果range.bounds
是一个数组:
- 下限值表示新增文档的起始值,与集合中已有的文档无关。
- 下限包含在内。
- 上限不包含在内。
$densify
不会过滤掉字段值超出指定范围的文档。
输出顺序
$densify
不保证输出文档的顺序。想要保证有序,可以使用$sort
对想要排序的字段进行排序。
举例
填充时间序列数据
创建一个包含四小时温度读数的weather
集合。
db.weather.insertMany( [
{
"metadata": {
"sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T00:00:00.000Z"),
"temp": 12
},
{
"metadata": {
"sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T04:00:00.000Z"),
"temp": 11
},
{
"metadata": {
"sensorId": 5578, "type": "temperature" },
"timestamp": ISODate("2021-05-18T08:00:00.000Z"),
"temp": 11
},
{