MongoDB - 排序/合并多个嵌入式文档
MongoDB - Sorting / Merging multiple embedded documents
我对 mongoldb 很陌生。
我有一个问题,我无法自己解决。
这是我的模型(针对这个问题的范围进行了简化)
account {
type: String
videos: [video],
images: [image]
}
video {
name: String,
length: Number,
Date: Date
}
image {
name: String,
size: Number
Date: Date
}
所以一个主模型帐户有两个嵌入文档:视频和图像。
我想(按此顺序):
- 按类型查询所有账户
- 按日期对视频和图像数组进行排序
- 获取图片和视频列表
- 限制输出以进行分页
是否可以或最好更改模型?
示例:
来源
[{
type: 2,
images:
[{ name: 'imagetest1', size: 3, Date: 2011-01-01},
{ name: 'imagetest2', size: 13, Date: 2011-02-02}],
videos:
[{ name: 'videotest1', length: 24, Date: 2011-01-07},
{ name: 'videotest2', length: 15, Date: 2011-03-02}] }
{
type: 2,
images:
[{ name: 'imagetest3', size: 3, Date: 2011-01-03},
{ name: 'imagetest4', size: 15, Date: 2011-01-06}],
videos:
[{ name: 'videotest3', length: 24, Date: 2011-02-05},
{ name: 'videotest4', length: 16, Date: 2011-02-04}]
},
{
type: 1,
images:
[{ name: 'imagetest5', size: 3, Date: 2011-01-03},
{ name: 'imagetest6', size: 15, Date: 2011-01-06}],
videos:
[{ name: 'videotest5', length: 24, Date: 2011-02-05},
{ name: 'videotest6', length: 16, Date: 2011-02-04}]
}]
MongoDB查询:
按类型查询账号:2,图片和视频按数据升序排列,最后将图片和视频合并到一个数组中。
输出
[{ name: 'imagetest1', size: 3, Date: 2011-01-01},
{ name: 'videotest1', length: 24, Date: 2011-01-02},
{ name: 'imagetest3', size: 3, Date: 2011-01-03},
{ name: 'videotest4', length: 16, Date: 2011-02-04},
{ name: 'videotest3', length: 24, Date: 2011-02-05},
{ name: 'imagetest4', size: 15, Date: 2011-01-06},
{ name: 'videotest1', length: 24, Date: 2011-01-07},
{ name: 'imagetest2', size: 13, Date: 2011-02-02},
{ name: 'videotest2', length: 15, Date: 2011-03-02}]
你可以在 MAP-REDUCE 的帮助下完成。
映射函数:
var map = function()
{
var doc = this.videos;
for( var i =0 ; i < this.images.length; i++ )
{
doc.push({name: this.images[i].name, length : this.images[i].size, Date: this.images[i].Date});
}
emit(this.type, doc );
}
归约函数:
var reduce = function(k,v)
{
var arr = v[0];
for( var j = 1 ; j < v.length; j++ )
{
arr = arr.concat(v[j]);
}
return arr;
}
查询:
db.accounts.mapReduce(
map,
reduce,
{
out : {inline: 1} ,
query : {type : 2} ,
sort : {Date : 1}
}
);
在 MongoDb 3.2 you can use $concatArrays 运算符的未来版本中可以轻松合并聚合管道中的数组。
db.accounts.aggregate([
{
$match : { type: 2 }
},
{
$project :
{
newArray:
{ $concatArrays: [ {$ifNull: ["$videos", [] ] }, {$ifNull: ["$images", [] ] } ] } ,
type : 1
}
},
{
$sort : {"$newArray.Date" : 1 }
}
]);
问题
在我看来,您必须将查询与需要它们的时间点相关联。
例如:您真的需要所有这些信息吗?通常情况下,你不会。因此,让我们剖析这些查询。这是我想出的
- 在给定时间点,匹配给定类型的用户有哪些?
- 对于给定用户,在给定时间点按日期排序的视频是什么?
- 对于给定用户,在给定时间点按日期排序的图像是什么?
- 对于给定用户的视频,大小为 y 的第 x 页的内容是什么?
- 对于给定用户的图像,网站 y 的第 x 页的内容是什么?
之所以这么拆分,纯粹是为了用户体验。假设给定用户打开一个显示他的图像和视频的网页。通过拆分问题,您可以通过 AJAX 独立加载它们——但用户至少已经有了他或她的页面。通过使查询尽可能简单,他们很可能会更快得到答复。那么,让我们看看实际的查询以及如何加快查询速度。
模特
我会简单地拆分这些模型,因为 a) 有 16MB 的 BSON 大小限制,b) 在默认的 mmapv1 存储引擎中,扩展文档的大小超过某个阈值会导致昂贵的文档迁移,c) 当你看,复杂的模型会导致复杂的查询来回答简单的问题。
所以,我们拆分我们的模型
用户模型
{
_id: new ObjectId(),
username: "SomeUserName",
type: "someType"
//… Whatever you deem appropriate
}
视频模型
{
_id: new ObjectId(),
owner: objectIdOfUser,
name: "cool video",
duration: msecsAsLong,
date: new ISODate()
}
由于我们关于视频的问题是针对 given 用户的,因此我们可以在此处使用隐式引用而不是嵌入。
图像模型
同样适用于图像:
{
_id: new ObjectId(),
owner: objectIdOfUser,
name: "Some image name",
size: bytesAsLong,
Date: new ISODate()
}
回答问题
"At a given point in time, what are the users matching a given type?"
查询很简单:
db.users.find({"type":typeToLookup})
优化此查询同样容易:
db.users.createIndex({"type":1})
"对于给定用户,在给定时间点按日期排序的 videos/images 是什么?"
由于我们拆分了问题,所以我们简化了一切。回答这个问题的查询也变得相当简单 因为我们有一个已知用户,我们想要获取 :
的视频
db.videos.find({ "owner": knownUserId }).sort({ "date":-1 })
降序或
db.videos.find({ "owner":knownUserId }).sort({ "date":-1 })
为升序。优化查询取决于您想要升序还是降序:
// For descending order
db.videos.createIndex({ "owner":1, "date":-1 })
// For ascending order
db.videos.createIndex({ "owner":1, "date":1 })
请注意,您可以将两个索引用于反之亦然的排序,但它们的效率不高。我倾向于做的是根据默认顺序创建索引,因为当顺序相反时,用户通常可以忍受很短的时间。
图像相应地工作。
对于给定用户的 videos/images,大小为 y 的页面 x 的内容是什么?
现在这是微不足道的。我们已经决定进行排序,比方说降序。现在,我们简单地使用 skip 和 limit。假设您的页面大小为 10,并且您希望按日期降序查看用户视频的第二页:
var pageSize = 10
var pageToDisplay = 2
var recordsToSkip = pageSize * (pageToDisplay - 1)
db.videos.find({ "owner": knownUserId }).sort({date:-1}).skip(recordsToSkip).limit(pageSize)
同样,图像会相应地工作。
为什么要使用这种方法?
正如所写,嵌入后,您可能会达到 MongoDB 施加的 BSON 文档大小限制。此外,我们避免了相当昂贵的文档迁移,并使复杂的查询变得非常简单,同时仍然可以回答相同的问题。
上面显示的示例很可能并不完全适合您的用例。但是您了解 "divide and conquer" 解决问题的方法。
我对 mongoldb 很陌生。
我有一个问题,我无法自己解决。
这是我的模型(针对这个问题的范围进行了简化)
account {
type: String
videos: [video],
images: [image]
}
video {
name: String,
length: Number,
Date: Date
}
image {
name: String,
size: Number
Date: Date
}
所以一个主模型帐户有两个嵌入文档:视频和图像。
我想(按此顺序):
- 按类型查询所有账户
- 按日期对视频和图像数组进行排序
- 获取图片和视频列表
- 限制输出以进行分页
是否可以或最好更改模型?
示例:
来源
[{
type: 2,
images:
[{ name: 'imagetest1', size: 3, Date: 2011-01-01},
{ name: 'imagetest2', size: 13, Date: 2011-02-02}],
videos:
[{ name: 'videotest1', length: 24, Date: 2011-01-07},
{ name: 'videotest2', length: 15, Date: 2011-03-02}] }
{
type: 2,
images:
[{ name: 'imagetest3', size: 3, Date: 2011-01-03},
{ name: 'imagetest4', size: 15, Date: 2011-01-06}],
videos:
[{ name: 'videotest3', length: 24, Date: 2011-02-05},
{ name: 'videotest4', length: 16, Date: 2011-02-04}]
},
{
type: 1,
images:
[{ name: 'imagetest5', size: 3, Date: 2011-01-03},
{ name: 'imagetest6', size: 15, Date: 2011-01-06}],
videos:
[{ name: 'videotest5', length: 24, Date: 2011-02-05},
{ name: 'videotest6', length: 16, Date: 2011-02-04}]
}]
MongoDB查询:
按类型查询账号:2,图片和视频按数据升序排列,最后将图片和视频合并到一个数组中。
输出
[{ name: 'imagetest1', size: 3, Date: 2011-01-01},
{ name: 'videotest1', length: 24, Date: 2011-01-02},
{ name: 'imagetest3', size: 3, Date: 2011-01-03},
{ name: 'videotest4', length: 16, Date: 2011-02-04},
{ name: 'videotest3', length: 24, Date: 2011-02-05},
{ name: 'imagetest4', size: 15, Date: 2011-01-06},
{ name: 'videotest1', length: 24, Date: 2011-01-07},
{ name: 'imagetest2', size: 13, Date: 2011-02-02},
{ name: 'videotest2', length: 15, Date: 2011-03-02}]
你可以在 MAP-REDUCE 的帮助下完成。
映射函数:
var map = function()
{
var doc = this.videos;
for( var i =0 ; i < this.images.length; i++ )
{
doc.push({name: this.images[i].name, length : this.images[i].size, Date: this.images[i].Date});
}
emit(this.type, doc );
}
归约函数:
var reduce = function(k,v)
{
var arr = v[0];
for( var j = 1 ; j < v.length; j++ )
{
arr = arr.concat(v[j]);
}
return arr;
}
查询:
db.accounts.mapReduce(
map,
reduce,
{
out : {inline: 1} ,
query : {type : 2} ,
sort : {Date : 1}
}
);
在 MongoDb 3.2 you can use $concatArrays 运算符的未来版本中可以轻松合并聚合管道中的数组。
db.accounts.aggregate([
{
$match : { type: 2 }
},
{
$project :
{
newArray:
{ $concatArrays: [ {$ifNull: ["$videos", [] ] }, {$ifNull: ["$images", [] ] } ] } ,
type : 1
}
},
{
$sort : {"$newArray.Date" : 1 }
}
]);
问题
在我看来,您必须将查询与需要它们的时间点相关联。
例如:您真的需要所有这些信息吗?通常情况下,你不会。因此,让我们剖析这些查询。这是我想出的
- 在给定时间点,匹配给定类型的用户有哪些?
- 对于给定用户,在给定时间点按日期排序的视频是什么?
- 对于给定用户,在给定时间点按日期排序的图像是什么?
- 对于给定用户的视频,大小为 y 的第 x 页的内容是什么?
- 对于给定用户的图像,网站 y 的第 x 页的内容是什么?
之所以这么拆分,纯粹是为了用户体验。假设给定用户打开一个显示他的图像和视频的网页。通过拆分问题,您可以通过 AJAX 独立加载它们——但用户至少已经有了他或她的页面。通过使查询尽可能简单,他们很可能会更快得到答复。那么,让我们看看实际的查询以及如何加快查询速度。
模特
我会简单地拆分这些模型,因为 a) 有 16MB 的 BSON 大小限制,b) 在默认的 mmapv1 存储引擎中,扩展文档的大小超过某个阈值会导致昂贵的文档迁移,c) 当你看,复杂的模型会导致复杂的查询来回答简单的问题。
所以,我们拆分我们的模型
用户模型
{
_id: new ObjectId(),
username: "SomeUserName",
type: "someType"
//… Whatever you deem appropriate
}
视频模型
{
_id: new ObjectId(),
owner: objectIdOfUser,
name: "cool video",
duration: msecsAsLong,
date: new ISODate()
}
由于我们关于视频的问题是针对 given 用户的,因此我们可以在此处使用隐式引用而不是嵌入。
图像模型
同样适用于图像:
{
_id: new ObjectId(),
owner: objectIdOfUser,
name: "Some image name",
size: bytesAsLong,
Date: new ISODate()
}
回答问题
"At a given point in time, what are the users matching a given type?"
查询很简单:
db.users.find({"type":typeToLookup})
优化此查询同样容易:
db.users.createIndex({"type":1})
"对于给定用户,在给定时间点按日期排序的 videos/images 是什么?"
由于我们拆分了问题,所以我们简化了一切。回答这个问题的查询也变得相当简单 因为我们有一个已知用户,我们想要获取 :
的视频db.videos.find({ "owner": knownUserId }).sort({ "date":-1 })
降序或
db.videos.find({ "owner":knownUserId }).sort({ "date":-1 })
为升序。优化查询取决于您想要升序还是降序:
// For descending order
db.videos.createIndex({ "owner":1, "date":-1 })
// For ascending order
db.videos.createIndex({ "owner":1, "date":1 })
请注意,您可以将两个索引用于反之亦然的排序,但它们的效率不高。我倾向于做的是根据默认顺序创建索引,因为当顺序相反时,用户通常可以忍受很短的时间。
图像相应地工作。
对于给定用户的 videos/images,大小为 y 的页面 x 的内容是什么?
现在这是微不足道的。我们已经决定进行排序,比方说降序。现在,我们简单地使用 skip 和 limit。假设您的页面大小为 10,并且您希望按日期降序查看用户视频的第二页:
var pageSize = 10
var pageToDisplay = 2
var recordsToSkip = pageSize * (pageToDisplay - 1)
db.videos.find({ "owner": knownUserId }).sort({date:-1}).skip(recordsToSkip).limit(pageSize)
同样,图像会相应地工作。
为什么要使用这种方法?
正如所写,嵌入后,您可能会达到 MongoDB 施加的 BSON 文档大小限制。此外,我们避免了相当昂贵的文档迁移,并使复杂的查询变得非常简单,同时仍然可以回答相同的问题。
上面显示的示例很可能并不完全适合您的用例。但是您了解 "divide and conquer" 解决问题的方法。