MongoDB - 排序/合并多个嵌入式文档

时间:2015-11-03 18:44:50

标签: mongodb sorting merge 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. 获取图片和视频列表
  5. 限制输出以进行分页
  6. 是否有可能或更好的改变模型?

    示例:

    来源

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

2 个答案:

答案 0 :(得分:1)

你可以借助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的未来版本中,您可以使用$concatArrays运算符轻松地合并聚合管道中的数组。

db.accounts.aggregate([
{
  $match : { type: 2 }
},
{
  $project : 
            { 
             newArray:  
                  { $concatArrays: [ {$ifNull: ["$videos", [] ] }, {$ifNull: ["$images", [] ] } ] } ,
             type : 1
            } 
},
{
  $sort : {"$newArray.Date" : 1 }
}
]);

答案 1 :(得分:1)

问题

从我的角度来看,您必须将查询与需要的时间点相关联。

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

  1. 在给定的时间点,与给定类型匹配的用户是什么?
  2. 对于给定用户,在给定时间点按日期排序的视频是什么?
  3. 对于给定用户,在给定时间点按日期排序的图像是什么?
  4. 对于给定用户的视频,x大小为x的内容是什么?
  5. 对于给定用户的图像,网站y的第x页的内容是什么?
  6. 我之所以将其拆分,只是用户体验。让我们假设一个给定的用户打开一个显示他的图像和视频的网页。通过拆分问题,您可以通过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()
    }
    

    由于我们关于视频的问题是针对给定的用户,我们可以在此使用隐式引用而不是嵌入。

    图像模型

    这同样适用于图像:

    {
      _id: new ObjectId(),
      owner: objectIdOfUser,
      name: "Some image name",
      size: bytesAsLong,
      Date: new ISODate()
    }
    

    回答问题

    &#34;在给定的时间点,匹配给定类型的用户是什么?&#34;

    查询非常简单:

    db.users.find({"type":typeToLookup})
    

    优化此查询同样容易:

    db.users.createIndex({"type":1})
    

    &#34; 对于给定用户,在给定时间点按日期排序的视频/图像是什么?&#34;

    由于我们分开了问题,我们简化了一切。回答这个问题的查询变得相当简单,因为我们有一个已知的用户,我们想要获取的视频:

    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 })
    

    请注意,您可以将两个索引用于反之亦然,但它们不会有效。我倾向于根据默认顺序创建索引,因为用户通常可以在订购时反转时间很短。

    图像相应地起作用。

    对于给定用户的视频/图像,x大小为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文档大小限制。此外,我们可以防止相当昂贵的文档迁移,并使复杂的查询非常容易,同时仍然可以回答相同的问题。

    上面显示的示例可能不完全适合您的用例。但是你得到了“分而治之”的图片。解决问题的方法。