MongoDB聚合:$densify

$densify阶段可以为文档序列中字段缺失的某些值创建新文档。其主要的用途有:

  • 补齐时间序列数据。
  • 为分组数据添加缺失值。
  • 为指定的值范围填充数据。

语法

$densify阶段的语法:

js 复制代码
{
   $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集合。

js 复制代码
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
   },
   {
       "metadata": { "sensorId": 5578, "type": "temperature" },
       "timestamp": ISODate("2021-05-18T12:00:00.000Z"),
       "temp": 12
   }
] )

下面使用$densify阶段来填补四小时间隔之间的空白,实现小时粒度的数据点:

js 复制代码
db.weather.aggregate( [
   {
      $densify: {
         field: "timestamp",
         range: {
            step: 1,
            unit: "hour",
            bounds:[ ISODate("2021-05-18T00:00:00.000Z"), ISODate("2021-05-18T08:00:00.000Z") ]
         }
      }
   }
] )

在这个例子中:

  • $densify阶段填充温度记录之前的空缺时间
    • field"timestamp"填充timestamp(时间戳)字段
    • range:
      • step:1表示timestamp字段的增量为1个单位
      • unit:hour表示timestamp字段填充为小时
      • bounds: [ ISODate("2021-05-18T00:00:00.000Z"), ISODate("2021-05-18T08:00:00.000Z") ]设置了要填充的时间范围

在下面的输出中,$densify阶段以小时为单位填充了00:00:0008:00:00之间的间隙:

js 复制代码
[
  {
    _id: ObjectId("618c207c63056cfad0ca4309"),
    metadata: { sensorId: 5578, type: 'temperature' },
    timestamp: ISODate("2021-05-18T00:00:00.000Z"),
    temp: 12
  },
  { timestamp: ISODate("2021-05-18T01:00:00.000Z") },
  { timestamp: ISODate("2021-05-18T02:00:00.000Z") },
  { timestamp: ISODate("2021-05-18T03:00:00.000Z") },
  {
    _id: ObjectId("618c207c63056cfad0ca430a"),
    metadata: { sensorId: 5578, type: 'temperature' },
    timestamp: ISODate("2021-05-18T04:00:00.000Z"),
    temp: 11
  },
  { timestamp: ISODate("2021-05-18T05:00:00.000Z") },
  { timestamp: ISODate("2021-05-18T06:00:00.000Z") },
  { timestamp: ISODate("2021-05-18T07:00:00.000Z") },
  {
    _id: ObjectId("618c207c63056cfad0ca430b"),
    metadata: { sensorId: 5578, type: 'temperature' },
    timestamp: ISODate("2021-05-18T08:00:00.000Z"),
    temp: 11
  }
  {
    _id: ObjectId("618c207c63056cfad0ca430c"),
    metadata: { sensorId: 5578, type: 'temperature' },
    timestamp: ISODate("2021-05-18T12:00:00.000Z"),
    temp: 12
  }
]

分区填充

创建一个coffee数据集,其中包含两种咖啡豆的数据:

js 复制代码
db.coffee.insertMany( [
   {
      "altitude": 600,
      "variety": "Arabica Typica",
      "score": 68.3
   },
   {
      "altitude": 750,
      "variety": "Arabica Typica",
      "score": 69.5
   },
   {
      "altitude": 950,
      "variety": "Arabica Typica",
      "score": 70.5
   },
   {
      "altitude": 1250,
      "variety": "Gesha",
      "score": 88.15
   },
   {
     "altitude": 1700,
     "variety": "Gesha",
     "score": 95.5,
     "price": 1029
   }
] )
填充范围内全部的值

下面的例子使用$densify为所有的咖啡variety填充altitude字段:

js 复制代码
db.coffee.aggregate( [
   {
      $densify: {
         field: "altitude",
         partitionByFields: [ "variety" ],
         range: {
            bounds: "full",
            step: 200
         }
      }
   }
] )

在上例的聚合中:

  • 根据variety对文档进行分区,创建了两个分组分别是'Arabica Typica'和Gesha咖啡。
  • 指定了full范围,即在每个分区的现有文档范围内对数据进行填充。
  • 指定了200step,即被创建的新文档的altitude间隔为200

聚合输出下面的文档:

js 复制代码
[
   {
     _id: ObjectId("618c031814fbe03334480475"),
     altitude: 600,
     variety: 'Arabica Typica',
     score: 68.3
   },
   {
     _id: ObjectId("618c031814fbe03334480476"),
     altitude: 750,
     variety: 'Arabica Typica',
     score: 69.5
   },
   { variety: 'Arabica Typica', altitude: 800 },
   {
     _id: ObjectId("618c031814fbe03334480477"),
     altitude: 950,
     variety: 'Arabica Typica',
     score: 70.5
   },
   { variety: 'Gesha', altitude: 600 },
   { variety: 'Gesha', altitude: 800 },
   { variety: 'Gesha', altitude: 1000 },
   { variety: 'Gesha', altitude: 1200 },
   {
     _id: ObjectId("618c031814fbe03334480478"),
     altitude: 1250,
     variety: 'Gesha',
     score: 88.15
   },
   { variety: 'Gesha', altitude: 1400 },
   { variety: 'Gesha', altitude: 1600 },
   {
     _id: ObjectId("618c031814fbe03334480479"),
     altitude: 1700,
     variety: 'Gesha',
     score: 95.5,
     price: 1029
   },
   { variety: 'Arabica Typica', altitude: 1000 },
   { variety: 'Arabica Typica', altitude: 1200 },
   { variety: 'Arabica Typica', altitude: 1400 },
   { variety: 'Arabica Typica', altitude: 1600 }
 ]
在所有分区填充值

下面使用$densify只对每个varietyaltitude字段的间隙进行填充:

js 复制代码
db.coffee.aggregate( [
   {
      $densify: {
         field: "altitude",
         partitionByFields: [ "variety" ],
         range: {
            bounds: "partition",
            step: 200
         }
      }
   }
] )

这个例子中的聚合:

  • 根据variety对文档进行分区,创建了两个分组分别是'Arabica Typica'和Gesha咖啡。
  • 指定分区范围,即在每个分区内对数据进行填充处理。
    • 对于Arabica Typica分区,范围是600~900
    • 对于Gesha分区,范围是1250~1700
  • 指定了200step,即被创建的新文档的altitude间隔为200

聚合输出下面的文档:

js 复制代码
[
   {
     _id: ObjectId("618c031814fbe03334480475"),
     altitude: 600,
     variety: 'Arabica Typica',
     score: 68.3
   },
   {
     _id: ObjectId("618c031814fbe03334480476"),
     altitude: 750,
     variety: 'Arabica Typica',
     score: 69.5
   },
   { variety: 'Arabica Typica', altitude: 800 },
   {
     _id: ObjectId("618c031814fbe03334480477"),
     altitude: 950,
     variety: 'Arabica Typica',
     score: 70.5
   },
   {
     _id: ObjectId("618c031814fbe03334480478"),
     altitude: 1250,
     variety: 'Gesha',
     score: 88.15
   },
   { variety: 'Gesha', altitude: 1450 },
   { variety: 'Gesha', altitude: 1650 },
   {
     _id: ObjectId("618c031814fbe03334480479"),
     altitude: 1700,
     variety: 'Gesha',
     score: 95.5,
     price: 1029
   }
 ]
相关推荐
桀桀桀桀桀桀29 分钟前
数据库中的用户管理和权限管理
数据库·mysql
superman超哥2 小时前
04 深入 Oracle 并发世界:MVCC、锁、闩锁、事务隔离与并发性能优化的探索
数据库·oracle·性能优化·dba
用户8007165452002 小时前
HTAP数据库国产化改造技术可行性方案分析
数据库
engchina2 小时前
Neo4j 和 Python 初学者指南:如何使用可选关系匹配优化 Cypher 查询
数据库·python·neo4j
engchina2 小时前
使用 Cypher 查询语言在 Neo4j 中查找最短路径
数据库·neo4j
尘浮生2 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
威哥爱编程2 小时前
SQL Server 数据太多如何优化
数据库·sql·sqlserver
小华同学ai3 小时前
AJ-Report:一款开源且非常强大的数据可视化大屏和报表工具
数据库·信息可视化·开源
Acrelhuang3 小时前
安科瑞5G基站直流叠光监控系统-安科瑞黄安南
大数据·数据库·数据仓库·物联网
十叶知秋4 小时前
【jmeter】jmeter的线程组功能的详细介绍
数据库·jmeter·性能测试