【问题标题】:MongoDB One to Many RelationshipMongoDB 一对多关系
【发布时间】:2014-10-18 14:26:46
【问题描述】:

我开始学习 MongoDB,有一次我问自己如何解决 MongoDB 中的“一对多”关系设计。在搜索时,我在其他帖子/文章中发现了许多 cmets,例如“你在思考关系”。 好的我同意。在某些情况下,例如信息重复不会成为问题,例如 CLIENTS-ORDERS 示例。

但是,假设您有以下表格:ORDERS,其中嵌入了 DETAIL 结构,其中包含客户购买的 PRODUCTS。 因此,为了一件事或另一件事,您需要更改已嵌入多个订单的产品名称(或另一种信息)。

最后,你不得不在 MongoDB 中进行一对多的关联(也就是说,将 ObjectID 字段作为链接到另一个集合),这样你就可以解决这个简单的问题,不是吗? 但是每次我发现一些关于此的文章/评论时,它都会说这将是 Mongo 的性能故障。有点失望

在 MongoDB 中是否有另一种方法来解决/设计此问题而不会出现性能故障?

【问题讨论】:

  • 我邀请您查看这些博文:blog.mongodb.org/post/88473035333/…(以及第 1 和第 2 部分)
  • 不要忘记 mongodb 是一个 NoSQL 数据库,所以如果你的数据库中有很多这样的关系,你可能应该选择一个 SQL 数据库,因为 mongodb 总是会在这方面表现得更少边。这篇博文解释得很好:sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/…
  • @Baptistel Wuaw !感谢您链接的精彩文章。这让我大开眼界,证实了我对 MongoDB 的假设是正确的。正如作者所说:“MongoDB 文档告诉你它擅长什么,而不强调它不擅长什么”。所以我觉得更好,所以把我的努力放在另一个地方。
  • @BaptisteL: Sarah Mei 可能是一个优秀的程序员,但她不是 MongoDB 专家。读起来好像她只在 2 个非常简单的 MongoDB 项目中工作,并制作了几个 beginner's mistakes。她嵌套了文档,尽管它是多对多的关系(剧集/演员)和具有无限元素的嵌套数组(社交提要)。为了她的辩护,手册可能没有像今天那样解释data modeling
  • 这个问题比较复杂。我发现文章有很好的解释blog.mongodb.org/post/87200945828/…

标签: mongodb


【解决方案1】:

问题是您过度规范化数据。订单由客户定义,该客户在给定时间点居住在特定地点,支付在订单时有效的特定价格(这可能在应用程序生命周期内发生重大变化,并且无论如何您都必须记录这些价格和几个其他参数仅在特定时间点有效。因此,要记录订单(双关语),您需要保留该特定时间点的所有数据. 举个例子:

{ _id: "order123456789",
  date: ISODate("2014-08-01T16:25:00.141Z"),
  customer: ObjectId("53fb38f0040980c9960ee270"),
  items:[ ObjectId("53fb3940040980c9960ee271"),
          ObjectId("53fb3940040980c9960ee272"),
          ObjectId("53fb3940040980c9960ee273")
         ],
 Total:400
 }

现在,只要客户和商品的详细信息都没有改变,您就可以重现此订单的发送地点、订单价格等。但是现在如果客户更改地址会发生什么?或者,如果物品的价格发生变化?您需要在各自的文档中跟踪这些更改。像这样存储订单会非常更容易且足够高效:

{
  _id: "order987654321",
  date: ISODate("2014-08-01T16:25:00.141Z"),
  customer: {
               userID: ObjectId("53fb3940040980c9960ee283"),
               recipientName: "Foo Bar"
               address: {
                          street: "742 Evergreen Terrace",
                          city: "Springfield",
                          state: null
                         }
            },
  items: [
    {count:1, productId:ObjectId("53fb3940040980c9960ee300"), price: 42.00 },
    {count:3, productId:ObjectId("53fb3940040980c9960ee301"), price: 0.99},
    {count:5, productId:ObjectId("53fb3940040980c9960ee302"), price: 199.00}
  ]
}

通过这种数据模型和聚合管道的使用,您有几个优势:

  1. 您无需单独跟踪客户的价格和地址、姓名更改或礼品购买情况 - 已记录在案。
  2. 使用聚合管道,您可以创建价格趋势,而无需单独存储定价数据。您只需将商品的当前价格存储在订单文档中即可。
  3. 即使是价格弹性、州/市营业额等复杂的聚合,也可以使用非常简单的聚合来完成。

一般而言,可以肯定地说,在面向文档的数据库中,每个属性或字段在未来可能会发生变化并且这种变化会产生不同的语义含义,都应该存储在文档中。未来可能发生变化但不涉及语义的所有内容(示例中的用户密码)都可以通过 GUID 链接。

【讨论】:

  • 嗨,Markus,感谢您抽出宝贵时间回复,但我认为我没有很好地解释我。我理解您的意思:以特定价格出售并在特定时间发送到一个地址的订单和详细信息。如果事实上您的解决方案是正确的,但我一直看到一种关系模式:我们必须还必须使用 Id。但我的意思是:假设您的产品名称有错字,并且它已经嵌入到许多文档中。所以最后,解决方案是有一个 Id 链接(比如在关系中),这样你就可以避免重复数据。那么拥有一个文档范式有什么意义呢?
  • 由于您没有将产品名称添加到其他文档中,而是仅添加在某个时间点有效的产品属性,这应该不是问题。链接对象不是问题,但必须非常小心地完成以保持数据有效性。所以在我的例子中,没有必要。如果您想绝对将产品名称放入订单文档中,您可以很容易地更新它们db.orders.update( {"items.name": withTypo }, { $set: { "items.$.name" : withoutTypo} } )
【解决方案2】:

一对多关系

在这种关系中,有很多很多实体或许多实体映射到一个实体。例如。: - 一个城市有很多人住在那个城市。假设NYC 拥有 800 万人。

让我们假设以下数据模型:

//city { _id: 1, name: 'NYC', area: 30, people: [{ _id: 1, name: 'name', gender: 'gender' ..... }, .... 8 million people data inside this array .... ] }

这行不通,因为这将是非常巨大的。让我们试着翻转头部。

//people { _id: 1, name: 'John Doe', gender: gender, city: { _id: 1, name: 'NYC', area: '30' ..... } }

现在这种设计的问题是,如果显然有多个人住在纽约市,那么我们对城市数据做了很多重复。

可能,对这些数据建模的最佳方法是使用真正的链接

//people { _id: 1, name: 'John Doe', gender: gender, city: 'NYC' } //city { _id: 'NYC', ... }

在这种情况下,people 集合可以链接到 city 集合。知道我们没有外键约束,我们必须保持一致。所以,这是一个一对多的关系。它需要 2 个集合。对于小的一对少(这也是一对多),关系就像博客文章到 cmets。评论可以作为数组嵌入到帖子文档中。

因此,如果确实是一对多,则 2 个集合最适合链接。但对于一对少数人来说,一个集合通常就足够了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-12
    • 2015-10-25
    • 2018-11-02
    • 2019-08-23
    • 2016-01-17
    • 2022-12-23
    相关资源
    最近更新 更多