以下页面讨论按需物化视图(On-Demand Materialized Views)。有关视图的讨论,请参见“视图”(Views)页面。
从MongoDB 4.2版本开始,MongoDB为聚合管道添加了$merge阶段。该阶段可以将管道结果合并到现有集合而不是完全替换集合。这个功能允许用户创建按需物化视图,在运行管道时可以更新输出集合的内容。
例如:
在2019年1月底,集合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函数定义了一个monthlybakesales物化视图,其中包含累计月度销售信息。在该示例中,该函数使用日期参数,仅更新从特定日期开始的月度销售信息。
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" } }
] );
};
$match阶段会过滤数据,仅处理大于或等于startDate的销售信息。
$group阶段按年-月对销售信息进行分组。此阶段输出的文档形式如下:
{ "_id" : "<YYYY-mm>", "sales_quantity" : <num>, "sales_amount" : <NumberDecimal> }
$merge阶段将输出写入monthlybakesales集合。
基于_id字段(未分片输出集合的默认值),该阶段检查聚合结果中的文档是否与集合中的现有文档匹配:
- 当有匹配项时(即集合中已经存在具有相同年-月的文档时),该阶段会用聚合结果中的文档替换现有文档。
- 当没有匹配项时,该阶段会将聚合结果中的文档插入到集合中(未匹配时的默认行为)。
- 执行初始运行
对于初始运行,您可以传递一个日期new ISODate("1970-01-01"):
updateMonthlySales(new ISODate("1970-01-01"));
初始运行后,monthlybakesales包含以下文档:即 db.monthlybakesales.find().sort( { _id: 1 } ) 返回以下结果:
{ "_id" : "2018-12", "sales_quantity" : 41, "sales_amount" : NumberDecimal("506") }
{ "_id" : "2019-01", "sales_quantity" : 86, "sales_amount" : NumberDecimal("896") }
- 刷新物化视图
假设到2019年2月的第一周,bakesales集合已经更新了新的销售信息;具体来说,是1月份和2月份的额外销售。
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") }
] )
要刷新1月和2月的monthlybakesales数据,请再次运行该函数,以新的ISODate("2019-01-01")作为起点重新运行聚合管道。
updateMonthlySales(new ISODate("2019-01-01"));
monthlybakesales的内容已经更新,以反映bakesales集合中最新的数据;即db.monthlybakesales.find().sort( { _id: 1 } )返回以下结果:
{ "_id" : "2018-12", "sales_quantity" : 41, "sales_amount" : NumberDecimal("506") }
{ "_id" : "2019-01", "sales_quantity" : 102, "sales_amount" : NumberDecimal("1142") }
{ "_id" : "2019-02", "sales_quantity" : 15, "sales_amount" : NumberDecimal("284") }
附加信息:
$merge阶段:
可以将输出写入相同或不同数据库中的集合。
如果输出集合不存在,则创建一个新集合。
可以将结果(插入新文档、合并文档、替换文档、保留现有文档、操作失败、使用自定义更新管道处理文档)合并到现有集合中。
可以将输出写入分片集合。输入集合也可以进行分片。
有关$merge的更多信息,请参见:
- $merge和可用选项的更多信息
- 示例:按需物化视图:初始创建
- 示例:按需物化视图:更新/替换数据
- 示例:仅插入新数据