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

4
我是一名有用的助手,可以为您进行翻译。以下是需要翻译的内容:

我正在管理一个建筑产品商店的MongoDB数据库。最紧急的集合是产品,对吧? 有相当多的产品,但它们都属于5-8个类别中的一个,然后属于一组小的子类别中的一个。

例如:

-Electrical
  *Wires
    p1
    p2
    ..
  *Tools
    p5
    pn
    ..
  *Sockets
    p11
    p23
    ..
-Plumber
  *Pipes
    ..
  *Tools
    ..
  PVC
    ..

我将在网站客户端使用Angular来展示整个产品目录,我考虑使用AJAX查询我想要的正确子集产品。
然后,我想知道是否应该管理一个唯一的集合,例如:
{
    
    MainCategory1: {


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


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


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

或者每个类别一个集合。文档数量可能不超过500。然而,我关心的是:

  • 快速的数据库响应,
  • 易于服务器端数据库查询,以及
  • 客户端Angular代码用于呈现结果到html。

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

我会执行哪些CRUD操作?

  • 插入产品,我还想有一种方法来获取每个新注册的自动生成的id(可能是连续的)。但是,正如它可能看起来自然的那样,我不会向用户提供_id

  • 查询子类别的整个文档集。可能首先只获取一些属性。

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

  • 修改产品的属性值


你需要什么数据库查询?如果使用索引,Mongodb是快速读取数据的数据库吗? - Nikolay Lukyanchuk
请检查我的编辑,@NikolayLukyanchuk - diegoaguilar
1个回答

9

我同意客户端应该得到最容易呈现的结果。然而,把类别嵌套到产品中仍然是一个坏主意。因为一旦你想要改变类别的名称,这将是一场灾难。如果你考虑可能的用例,例如:

  • 列出所有类别
  • 查找某个类别的所有子类别
  • 查找某个类别中的所有产品

你会发现使用你的数据结构很难完成这些事情。

在我的当前项目中,我也遇到了同样的情况。所以这是我为你提供的参考做法。
首先,类别应该是单独的集合。不要嵌套类别,因为这将使查找所有子类别的过程变得复杂。找到所有子类别的传统方法是维护一个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"
}

使用idPath时,parentId不再必要。这样做是为了让您更容易理解结构。
如果您需要查找类别100的所有子类别,只需执行以下查询:

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

如果将类别存在一个单独的集合中,那么在您的产品中,您需要通过_id引用它们,就像在使用关系型数据库时一样。例如:

{
    ... // other fields of product
    categories: [100, 101, 102, ...]
}

现在如果您想找到某个分类中的所有产品:
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
    }
})

幸运的是,类别集合通常非常小,只有几百条记录(或者几千条)。而且它们变化不大。因此,您可以始终将类别的实时副本存储在内存中,并将其构建为嵌套对象,例如:

[{
    id: 100,
    name: "level 1 category",
    ... // other fields
    subcategories: [{
        id: 101,
        ... // other fields
        subcategories: [...]
    }, {
        id: 103,
        ... // other fields
        subcategories: [...]
    },
    ...]
}, {
    // another top1 category
}, ...]

您可能希望每隔几个小时刷新此副本,因此:
setTimeout(3600000, function() {
    // refresh your memory copy of categories.
});

这是我现在想到的全部内容。希望能对你有所帮助。
编辑:
  • to provide int ID for each user, $inc and findAndModify is very useful. you may have a idSeed collection:

    {
        _id: ...,
        seedValue: 1,
        forCollection: "user"
    }
    

    When you want to get an unique ID:

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

    The findAndModify is an atomic operator provided by mongodb. It will guarantee thread safety. and the find and modify actually happens in a "transaction".

  • 2nd question is in my answer already.
  • query subsets of properties is described with mongodb Manual. NodeJS API is almost the same. Read the document of projection parameter.
  • update subsets is also supported by $set of mongodb operator.

谢谢!我真的在阅读并跟随它。你能检查一下我的最后编辑吗?我提到了我将要做的CRUD,还有每个注册的代码生成。 - diegoaguilar
1
我编辑了我的回答。第一个问题有点棘手,所以我展示了如何解决它。接下来的三个问题只是常规的mongodb操作,所以我留下了两个文档供您阅读。 - yaoxing
谢谢!为什么在函数中将{$inc: {seedValue: 1}}作为参数传递?据我理解,我将首先创建一个带有种子值的虚拟集合(可能全部为1)。然后在插入之前,我会获取下一个ID(已经增加了)? - diegoaguilar
运算符$inc会将seedValue增加1,如果这就是你的问题。在回调函数中返回更新前的值。因此,每次调用此代码时,它都会给您一个唯一的整数ID。 - yaoxing
谢谢!还有一个问题:我总是可以通过ajax接收整个文档,包括_id,但是当再次查询时,我可以将ID_id关联起来,并按最后一项进行查询,对吗? :)我是Mongo的新手,我喜欢它并且现在使用它,因为我喜欢JS Obecjts / JSON,而SQL会占用大量RAM ... - diegoaguilar
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接