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
}

所以一个主模型帐户有两个嵌入文档:视频和图像。

我想(按此顺序):

  1. 按类型查询所有账户
  2. 按日期对视频和图像数组进行排序
  3. 获取图片和视频列表
  4. 限制输出以进行分页

是否可以或最好更改模型?

示例:

来源

[{ 
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 }
}
]);

问题

在我看来,您必须将查询与需要它们的时间点相关联。

例如:您真的需要所有这些信息吗?通常情况下,你不会。因此,让我们剖析这些查询。这是我想出的

  1. 在给定时间点,匹配给定类型的用户有哪些?
  2. 对于给定用户,在给定时间点按日期排序的视频是什么?
  3. 对于给定用户,在给定时间点按日期排序的图像是什么?
  4. 对于给定用户的视频,大小为 y 的第 x 页的内容是什么?
  5. 对于给定用户的图像,网站 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" 解决问题的方法。