我应该如何为嵌套文档建模MongoDB集合?

die*_*lar 1 database-design mongodb nosql node.js

我正在为建筑产品商店管理MongoDB数据库.最直接的系列是产品,对吧?有很多产品,但是它们都属于5-8个类别中的一个,然后属于一小部分子类别中的一个子类.

例如:

-Electrical
  *Wires
    p1
    p2
    ..
  *Tools
    p5
    pn
    ..
  *Sockets
    p11
    p23
    ..
-Plumber
  *Pipes
    ..
  *Tools
    ..
  PVC
    ..
Run Code Online (Sandbox Code Playgroud)

我将在网站客户端使用Angular来显示整个产品目录,我考虑使用AJAX来查询我想要的正确产品子集.

然后,我想知道我是否应该只管理一个集合:

{

    MainCategory1: {


        SubCategory1: {
        {},{},{},{},{},{},{}
        }
        SubCategory2: {
        {},{},{},{},{},{},{}
        }
        SubCategoryn: {
        {},{},{},{},{},{},{}
        }               
    },
    MainCategory2: {


        SubCategory1: {
        {},{},{},{},{},{},{}
        }
        SubCategory2: {
        {},{},{},{},{},{},{}
        }
        SubCategoryn: {
        {},{},{},{},{},{},{}
        }               
    },  
    MainCategoryn: {


        SubCategory1: {
        {},{},{},{},{},{},{}
        }
        SubCategory2: {
        {},{},{},{},{},{},{}
        }
        SubCategoryn: {
        {},{},{},{},{},{},{}
        }               
    }   
}
Run Code Online (Sandbox Code Playgroud)

或每个类别一个集合.文件数量可能不会高于500.但我关心的是:

  • 快速DB回答,
  • 简单的服务器端DB查询,和
  • 用于将结果呈现为html的客户端角度代码.

我正在使用mongodb node.js模块,而不是Mongoose.

我将做什么CRUD操作?

  • 插入产品,我也希望能够为每个新寄存器获取自动生成的ID(可能是顺序的).但是,因为它似乎很自然,我不会向用户提供_id.

  • 查询子类别的整个文档集.也许最初只是获得一些属性.

  • 特别是查询文档(产品)的整个或特定属性子集.

  • 修改产品的属性值.

yao*_*ing 5

我同意客户端应该获得最简单的渲染结果.但是,将类别嵌入到产品中仍然是一个坏主意.权衡是一旦你想改变,例如,一个类别的名称,它将是一场灾难.如果你考虑可能的用例,例如:

  • 列出所有类别
  • 找到某个类别的所有子类别
  • 查找特定类别的所有产品

你会发现很难用你的数据结构做这些事情.

我目前的项目情况相同.所以这就是我的建议供你参考.
首先,类别应该在一个单独的集合中.不要将类别嵌套到彼此中,因为这会使查找所有子类别的过程复杂化.查找所有子类别的传统方法是维护idPath属性.例如,您的类别分为3个级别:

{
    _id: 100,
    name: "level1 category"
    parentId: 0,  // means it's the top category
    idPath: "0-100"
}
{
    _id: 101,
    name: "level2 category"
    parentId: 100,
    idPath: "0-100-101"
}
{
    _id: 102,
    name: "level3 category"
    parentId: 101,
    idPath: "0-100-101-102"
}
Run Code Online (Sandbox Code Playgroud)

注意idPath,不再需要parentId.这对您来说更容易理解结构.
一旦您需要找到类别100的所有子类别,只需执行查询:

db.collection("category").find({_id: /^0-100-/}, function(err, doc) {
    // whatever you want to do
})
Run Code Online (Sandbox Code Playgroud)

将类别存储在单独的集合中,在产品中,您需要通过_id引用它们,就像我们使用RDBMS时一样.例如:

{
    ... // other fields of product
    categories: [100, 101, 102, ...]
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您想查找特定类别的所有产品:

db.collection("category").find({_id: new RegExp("/^" + idPath + "-/"}, function(err, categories) {
    var cateIds = _.pluck(categories, "_id"); // I'm using underscore to pluck category ids
    db.collection("product").find({categories: { $in: cateIds }}, function(err, products) {
        // products are here
    }
})
Run Code Online (Sandbox Code Playgroud)

幸运的是,类别集合通常非常小,内部(或数千)只有数百条记录.它并没有太大的变化.因此,您始终可以在内存中存储类别的实时副本,并且可以将其构造为嵌套对象,如:

[{
    id: 100,
    name: "level 1 category",
    ... // other fields
    subcategories: [{
        id: 101,
        ... // other fields
        subcategories: [...]
    }, {
        id: 103,
        ... // other fields
        subcategories: [...]
    },
    ...]
}, {
    // another top1 category
}, ...]
Run Code Online (Sandbox Code Playgroud)

您可能希望每隔几个小时刷新一次此副本,因此:

setTimeout(3600000, function() {
    // refresh your memory copy of categories.
});
Run Code Online (Sandbox Code Playgroud)

这就是我现在想到的一切.希望能帮助到你.

编辑:

  • 为每个用户提供int ID,$ incfindAndModify非常有用.你可能有一个idSeed集合:

    {
        _id: ...,
        seedValue: 1,
        forCollection: "user"
    }
    
    Run Code Online (Sandbox Code Playgroud)

    如果您想获得唯一ID:

    db.collection("idSeed").findAndModify({forCollection: "user"}, {}, {$inc: {seedValue: 1}}, {}, function(err, doc) {
        var newId = doc.seedValue;
    });
    
    Run Code Online (Sandbox Code Playgroud)

    findAndModify是mongodb提供的原子操作符.它将保证线程安全.并且查找和修改实际上发生在"事务"中.

  • 第二个问题已在我的答案中.
  • 使用mongodb Manual描述属性的查询子集.NodeJS API几乎相同.阅读projection参数文档.
  • $ set mongodb运算符也支持更新子集.