我在改变我用于我使用Mongo DB构建的时间序列数据库的模式时遇到了一些问题。目前,我有如下所示的记录:
{
"_id" : 20,
"name" : "Bob,
"location" : "London",
"01/01/1993" : {
"height" : "110cm",
"weight" : "60kg",
},
"02/01/1993" : {
"height" : "112cm",
"weight" : "61kg",
}
}
我希望使用聚合框架为每个" person"创建几个记录,每个记录一个" time-value"原始记录中的子文档:
{
"_id" : 20,
"name" : "Bob,
"date" : "01/01/1993"
"location" : "London",
"height" : "110cm",
"weight" : "60kg",
},
{
"_id" : 20,
"name" : "Bob,
"date" : "02/01/1993"
"location" : "London",
"height" : "112cm",
"weight" : "61kg",
}
在为每条记录添加大量时间序列值时,新方案应该更有效率,我不应该遇到最大文档大小错误!
非常感谢有关如何使用Mongo DB聚合管道执行此操作的任何帮助!
答案 0 :(得分:1)
虽然聚合框架的现代版本中有功能可以允许您执行此类操作,但里程可能会有所不同,以确定它是否真的是最佳解决方案。
本质上,您可以创建一个条目数组,其中包含“不包含”其他顶级键的文档键,然后这些键将包含在文档中。然后可以使用$unwind
处理该数组,并将整个结果重新整理为新文档:
db.getCollection('input').aggregate([
{ "$project": {
"name": 1,
"location": 1,
"data": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "d",
"cond": {
"$not": { "$in": [ "$$d.k", ["_id","name","location"] ] }
}
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
[{ "k": "id", "v": "$_id" },
{ "k": "name", "v": "$name" },
{ "k": "location", "v": "$location" },
{ "k": "date", "v": "$data.k" }],
{ "$objectToArray": "$data.v" }
]
}
}
}},
{ "$out": "output" }
])
或者在所生成的数组元素中的初始$project
中进行所有重塑:
db.getCollection('input').aggregate([
{ "$project": {
"_id": 0,
"data": {
"$map": {
"input": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "d",
"cond": {
"$not": { "$in": [ "$$d.k", ["_id", "name", "location"] ] }
}
}
},
"as": "d",
"in": {
"$arrayToObject": {
"$concatArrays": [
{ "$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "r",
"cond": { "$in": [ "$$r.k", ["_id", "name", "location"] ] }
}},
[{ "k": "date", "v": "$$d.k" }],
{ "$objectToArray": "$$d.v" }
]
}
}
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } },
{ "$out": "output" }
])
因此,您使用$objectToArray
和$filter
来创建实际包含每个日期数据点的键的数组。
在$unwind
之后,我们基本上对“数组格式”中的一组命名键应用$arrayToObject
,以便为$replaceRoot
构造newRoot
,然后写入新集合,作为使用$out
的每个数据密钥的一个新文档。
这可能只会帮助您解决问题,因为您确实应该将"date"
数据更改为BSON日期。它占用的存储空间更少,也更容易查询。
var updates = [];
db.getCollection('output').find().forEach( d => {
updates.push({
"updateOne": {
"filter": { "_id": d._id },
"update": {
"$set": {
"date": new Date(
Date.UTC.apply(null,
d.date.split('/')
.reverse().map((e,i) => (i == 1) ? parseInt(e)-1: parseInt(e) )
)
)
}
}
}
});
if ( updates.length >= 500 ) {
db.getCollection('output').bulkWrite(updates);
updates = [];
}
})
if ( updates.length != 0 ) {
db.getCollection('output').bulkWrite(updates);
updates = [];
}
当然,如果您的MongoDB服务器缺少这些聚合功能,那么最好通过首先迭代循环将输出写入新集合:
var output = [];
db.getCollection('input').find().forEach( d => {
output = [
...output,
...Object.keys(d)
.filter(k => ['_id','name','location'].indexOf(k) === -1)
.map(k => Object.assign(
{
id: d._id,
name: d.name,
location: d.location,
date: new Date(
Date.UTC.apply(null,
k.split('/')
.reverse().map((e,i) => (i == 1) ? parseInt(e)-1: parseInt(e) )
)
)
},
d[k]
))
];
if ( output.length >= 500 ) {
db.getCollection('output').insertMany(output);
output = [];
}
})
if ( output.length != 0 ) {
db.getCollection('output').insertMany(output);
output = [];
}
在任何一种情况下,我们都希望将Date.UTC
应用于现有“字符串”基于日期的反向字符串元素,并获得一个可以投射到BSON日期的值。
聚合框架本身不允许转换类型,因此该部分的唯一解决方案(并且它是必要部分)是实际循环和更新,但是使用表单至少可以使循环和更新更有效。
这两种情况都会给你相同的结束输出:
/* 1 */
{
"_id" : ObjectId("599275b1e38f41729f1d64fe"),
"id" : 20.0,
"name" : "Bob",
"location" : "London",
"date" : ISODate("1993-01-01T00:00:00.000Z"),
"height" : "110cm",
"weight" : "60kg"
}
/* 2 */
{
"_id" : ObjectId("599275b1e38f41729f1d64ff"),
"id" : 20.0,
"name" : "Bob",
"location" : "London",
"date" : ISODate("1993-01-02T00:00:00.000Z"),
"height" : "112cm",
"weight" : "61kg"
}