使用MongoDB中的属性表示多对多关系的最佳模型

时间:2014-08-16 22:07:10

标签: javascript mysql mongodb meteor nosql

最多' mongo'表示具有属性的多对多关系的方式?

例如:

简介


MYSQL表

people => firstName, lastName, ...

Movies => name, length ..

peopleMovies => movieId, personId, language, role

解决方案1 ​​


将人们嵌入电影......?

在MongoDB中,我理解它对denormalize and embed有好处,但我不想让embed人加入电影,它在逻辑上没有任何意义。因为人们不一定只属于电影。

解决方案2


PeopleMovies将是两个单独的集合。 People =>嵌入[{movieId: 12, personId: 1, language: "English", role: "Main"} ...]

Movies =>嵌入[{movieId: 12, personId: 1, language: "English", role: "Main"} ...]

此解决方案的问题在于,当我们要为特定role更新某人movie时,我们需要运行两个更新查询以确保数据在在两个集合中同步。

解决方案3


我们也可以做一些更像关系的事情,并最终得到三个集合

People => firstName, lastName, ... Movies => name, length .. Castings => movieId, personId, language, role

问题在于,由于MongoDB中缺少连接语句,所以需要3 queries来自人 - >电影,反之亦然。

以下是我的问题,有哪些其他方法可以在MongoDBNoSQL方式中对此类内容进行建模。就所提供的解决方案而言,哪一个在mongo中的性能和惯例方面是最好的。

2 个答案:

答案 0 :(得分:14)

meteor的API在很多方面鼓励平面关系文档,但MongoDB是一个非关系型数据存储。不幸的是,这种冲突留给了开发人员解决的练习。

模式结构和连接的概念是一个在单个答案中涵盖的巨大主题,因此我将尝试尽可能简洁。

您应该选择关系模型的原因

假设您有评论和发布数据。考虑如果您在帖子中嵌入评论会发生什么。

  • DDP对文档进行操作。每次添加同一帖子中的新评论时,都会发送所有评论。

  • allowdeny规则对文档进行操作。期望相同的规则同时适用于帖子和评论可能是不合理的。

  • 出版物往往在收藏方面更有意义。在上面的场景中,我们无法轻松发布独立于其帖子的评论列表。

  • 关系数据库存在的原因很充分。其中之一就是避免第二种解决方案中固有的多重修改问题。

您应该选择嵌入式模型的原因

  • MongoDB本身不支持联接,并且没有核心软件包可以生成响应式联接。

推荐

使用您的第三个解决方案。根据我的经验,选择关系模型的原因远远超过数据存储所施加的限制。当然,克服缺少连接并不容易,但痛苦很可能只与少数发布功能隔离开来。以下是我强烈推荐的一些资源:

如果您需要更多信息,请在下方发表评论,我会更新我的答案。

答案 1 :(得分:1)

我认为你应该对你的收藏品进行非规范化。设计MongoDB集合和文档时,重要的一点是考虑您的观点。显示视图需要哪些数据?我们的想法是,您应该尝试将这些数据作为文档的一部分。

例如,在您的情况下,您可能想要显示有关电影信息的Movies视图。但是关于电影的那个页面可能只需要关于每个人的基本信息(名字,姓氏,照片URL)。不是所有其他的东西。反之亦然,关于一个人的页面可能会列出所有电影,但同样只需要有关每部电影的一部分信息,如标题,年份和海报照片URL。

所以一个选项是拥有两个集合,但是然后嵌入(denormalize)集合之间需要的那些少数字段。例如,Movies集合将有一个字段people,它将是一个子文档数组。并且People集合将具有movies字段,该字段将是一个子文档数组,其中包含您想要指定角色的额外字段等等。

因此文档可能如下所示。对于电影:

{
  _id: "AAA",
  title: "...",
  year: 2015,
  length: 120,
  posterURL: "...",
  people: [
    {
      person: {
        _id: "BBB",
        firstName: "...",
        lastName: "...",
        photoURL: "..."
      },
      role: "..."
    }
  ]
}

对于人们:

{
  _id: "BBB",
  firstName: "...",
  lastName: "...",
  photoURL: "...",
  movies: [
    {
      _id: "AAA",
      title: "...",
      year: 2015,
      posterURL: "..."
    }
  ]
}

当然,问题是如何保持这些字段同步。如果您更新电影的海报照片URL,您希望在所有个人文档中更新它。为了解决这个问题,我们开发了PeerDB,一个用于定义集合之间关系的包,然后确保它们保持同步。

所以在你的情况下,我会在CoffeeScript中的PeerDB中定义这样的集合:

class People extends Document
  @Meta
    name: 'People'

class Movies extends Document
  @Meta
    name: 'Movies'
    fields: =>
      people: [
        person: @ReferenceField People, ['firstName', 'lastName', 'photoURL'], true, 'movies', ['title', 'year', 'posterURL']
      ]

简而言之,此定义表明people.person字段应该是对People集合的引用,并且与firstNamelastNamephotoURL保持同步。此外,应在字​​段People下的movies文档中使用titleyearposterURL进行反向引用字段。

非常简单。但是有一些缺点。阵列可能变得非常大(可能不是电影和人,但对于其他一些数据),这可能使文档对于MongoDB每文档限制而言太大(目前为16 MB)。此外,如果您观察到,您会看到People文档中没有关于电影列表中角色的信息。这是因为角色不是引用文档的一部分,但它是引用旁边的内容。如果您想要在人物页面/视图中显示某个人的电影角色怎么办?

所以,也许最好有三个集合,一个用于电影的基本信息,另一个用于人,然后是人与电影之间关系的集合。所以数据可能就像电影一样:

{
  _id: "AAA",
  title: "...",
  year: 2015,
  length: 120,
  posterURL: "..."
}

对于人们:

{
  _id: "BBB",
  firstName: "...",
  lastName: "...",
  photoURL: "..."
}

铸造:

{
  _id: "...",
  movie: {
    _id: "AAA",
    title: "...",
    year: 2015,
    posterURL: "..."
  },
  person: {
    _id: "BBB",
    firstName: "...",
    lastName: "...",
    photoURL: "..."
  },
  role: "..."
}

和PeerDB定义:

class People extends Document
  @Meta
    name: 'People'

class Movies extends Document
  @Meta
    name: 'Movies'

class Casting extends Document
  @Meta
    name: 'Casting'
    fields: =>
      person: @ReferenceField People, ['firstName', 'lastName', 'photoURL']
      movie: @ReferenceField Movies, ['title', 'year', 'posterURL']
然后,PeerDB会确保事情保持同步。如果从数据库中删除电影或人员,它也会删除投射文档。

然后,您可以制作有效且不需要动态构建相关查询的Meteor发布。您只需发布Casting集合即可。您甚至可以查询某些条件。例如,您想要显示按firstNamelastName排序的所有导演及其电影吗?只有一个查询可能。