MongoDB On-Demand Materialized Views

什么是按需的物化视图

这个功能从MongoDB 4.2开始伴随着聚合管道的新stage特性$merge引入的.$merge stage能将聚合管道的结果合并到一个现有的collection中,借助$merge这个特性用户能创建按需物化视图(on-demand materialized view)

按需物化视图的原理: 每次运行聚合管道,就会将结果输出到collection中

Starting in version 4.2, MongoDB adds the $merge stage for the aggregation pipeline. This stage can merge the pipeline results to an existing collection instead of completely replacing the collection. This functionality allows users to create on-demand materialized views, where the content of the output collection can be updated each time the pipeline is run.

用途

  • 数据统计操作: 对某个维度的信息做处理,比如每天对销售清单表里面的数据做一次汇总,算出每天的销售额

样例

我们按照官网上的样例做解读:bakesales里面记录了蛋糕的销量情况,我们需要每天统计一次每月/每天的销售额

原始数据

db.bakesales.insertMany( [
   { date: new ISODate("2018-12-01"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
   { date: new ISODate("2018-12-02"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("90") },
   { date: new ISODate("2018-12-02"), item: "Cake - Red Velvet", quantity: 10, amount: new NumberDecimal("200") },
   { date: new ISODate("2018-12-04"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
   { date: new ISODate("2018-12-04"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
   { date: new ISODate("2018-12-05"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
   { date: new ISODate("2019-01-25"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
   { date: new ISODate("2019-01-25"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
   { date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
   { date: new ISODate("2019-01-26"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
   { date: new ISODate("2019-01-26"), item: "Cake - Carrot", quantity: 2, amount: new NumberDecimal("36") },
   { date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
   { date: new ISODate("2019-01-27"), item: "Pie - Chocolate Cream", quantity: 1, amount: new NumberDecimal("20") },
   { date: new ISODate("2019-01-27"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("80") },
   { date: new ISODate("2019-01-27"), item: "Tarts - Apple", quantity: 3, amount: new NumberDecimal("12") },
   { date: new ISODate("2019-01-27"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
   { date: new ISODate("2019-01-27"), item: "Cake - Carrot", quantity: 5, amount: new NumberDecimal("36") },
   { date: new ISODate("2019-01-27"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
   { date: new ISODate("2019-01-28"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
   { date: new ISODate("2019-01-28"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
   { date: new ISODate("2019-01-28"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
] );
复制代码

创建按需物化视图

我们建立了一个物化视图名称为updateMonthlySales,它可以统计从startDate开始的月度销售情况,比如销售的数量和金额;将结果保存到monthlybakesales,当monthlybakesales中已经有了旧的统计信息,那么就用最新的信息更新旧的

think_db> updateMonthlySales = function(startDate) {
    db.bakesales.aggregate( [
       { $match: { date: { $gte: startDate } } },
       { $group: { _id: { $dateToString: { format: "%Y-%m", date: "$date" } }, sales_quantity: { $sum: "$quantity"}, sales_amount: { $sum: "$amount" } } },
       { $merge: { into: "monthlybakesales", whenMatched: "replace" } }
    ] );
 };
 
[Function: updateMonthlySales]
复制代码

执行第一次结算,从1970-01-01开始

# 执行
think_db> updateMonthlySales(new ISODate("1970-01-01"));
# 查询
think_db> db.monthlybakesales.find()
[
  {
    _id: '2018-12',
    sales_quantity: 41,
    sales_amount: Decimal128("506")
  },
  {
    _id: '2019-01',
    sales_quantity: 86,
    sales_amount: Decimal128("896")
  }
]
think_db> 

复制代码

再次插入销售数据,模拟新的交易产生了

db.bakesales.insertMany( [
   { date: new ISODate("2019-01-28"), item: "Cake - Chocolate", quantity: 3, amount: new NumberDecimal("90") },
   { date: new ISODate("2019-01-28"), item: "Cake - Peanut Butter", quantity: 2, amount: new NumberDecimal("32") },
   { date: new ISODate("2019-01-30"), item: "Cake - Red Velvet", quantity: 1, amount: new NumberDecimal("20") },
   { date: new ISODate("2019-01-30"), item: "Cookies - Chocolate Chip", quantity: 6, amount: new NumberDecimal("24") },
   { date: new ISODate("2019-01-31"), item: "Pie - Key Lime", quantity: 2, amount: new NumberDecimal("40") },
   { date: new ISODate("2019-01-31"), item: "Pie - Banana Cream", quantity: 2, amount: new NumberDecimal("40") },
   { date: new ISODate("2019-02-01"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
   { date: new ISODate("2019-02-01"), item: "Tarts - Apple", quantity: 2, amount: new NumberDecimal("8") },
   { date: new ISODate("2019-02-02"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
   { date: new ISODate("2019-02-02"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
   { date: new ISODate("2019-02-03"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") }
] )
复制代码

重新执行物化视图,我们可以看到已经进行了结果的更新

# 执行
think_db> updateMonthlySales(new ISODate("1970-01-01"));
# 查询
think_db> db.monthlybakesales.find()
[
  {
    _id: '2018-12',
    sales_quantity: 41,
    sales_amount: Decimal128("506")
  },
  {
    _id: '2019-01',
    sales_quantity: 102,
    sales_amount: Decimal128("1142")
  },
  {
    _id: '2019-02',
    sales_quantity: 15,
    sales_amount: Decimal128("284")
  }
]

复制代码

总结:

  • 按需物化视图首先是按需计算的,每次需要最新的数据都需要执行一次计算,这样结果才能更新,数据是由计算产生了,按需物化视图并不拥有实际的数据
  • 定义按需物化视图本质上是定义一个函数,函数逻辑由聚合管道组成

おすすめ

転載: juejin.im/post/7049691632924360741