【问题标题】:Best Model for Representing Many to Many relationships with attributes in MongoDB用 MongoDB 中的属性表示多对多关系的最佳模型
【发布时间】:2014-10-10 06:04:32
【问题描述】:

表示具有属性的多对多关系的最“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"} ...]

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

解决方案 3


我们还可以做一些更相关的事情,最终得到三个集合

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

问题在于,由于 MongoDB 中缺少 join 语句,因此需要 3 queries 才能从人 -> 电影,反之亦然。

这是我的问题,还有哪些其他方法可以以MongoDB 和更多NoSQL 的方式对此类事物进行建模。就提供的解决方案而言,在 mongo 中哪一个在性能和约定方面是最好的。

【问题讨论】:

  • 虽然那篇文章有一些不错的地方,但我觉得它完全遗漏了很多东西。就像将 cast_members 嵌入到 tv_shows 中一样。如果您想查找特定演员的 tv_shows 怎么办?
  • MongoDB 不是为存储关系数据而设计的。 Meteor 的许多 API(模板迭代、DDP、允许/拒绝、发布)不鼓励使用嵌入式文档。根据我的经验,与 Meteor 战斗比与 Mongo 战斗要困难得多。我的一般建议是咬紧牙关并以相关方式存储文档(解决方案 3)。 Discover Meteor 的“反规范化”一章也涵盖了这个主题。
  • @DavidWeldon 无法检查资源,因为我需要购买它。我想问你是否有一些关于如何使用流星中的关系模型设置查询的资源(使用 pub/sub 方法)

标签: javascript mysql mongodb meteor nosql


【解决方案1】:

在许多方面,meteor 的 API 都鼓励平面关系文档,但 MongoDB 是一种非关系数据存储。不幸的是,这个冲突留给开发者解决。

模式结构和连接的概念是一个巨大的话题,需要在一个答案中涵盖,所以我会尽量简洁。

应该选择关系模型的原因

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

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

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

  • 就收藏而言,出版物往往更有意义。在上述情况下,我们无法轻松发布独立于他们帖子的 cmets 列表。

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

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

  • MongoDB 本身不支持联接,并且没有生成反应联接的核心包。

建议

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

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

【讨论】:

  • 虽然“发布复合”在发布相关数据方面做得相当不错,但客户当然只能访问他们个人集合中的数据——关系丢失了。因此,客户端逻辑必须了解数据结构才能再次执行此操作,这很可惜。
  • 同意。在您的应用中添加model layer 会有所帮助。
  • 大卫太棒了,谢谢你的指点!它看起来是一种将相关数据整合到单个集合中并将其封装到单个代码块中的好方法。我很高兴我将我的临时评论放到了您的答案中以发现这一点!
【解决方案2】:

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

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

因此,一种选择是拥有两个集合,然后仅在集合之间嵌入(非规范化)您需要的那几个字段。例如,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,这是一个定义集合之间关系的包,然后确保它们保持同步。

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

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 保持同步。此外,在movies字段下的People文档中应设置反向引用字段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 排序的所有导演及其电影?只需一个查询即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-24
    • 2020-12-05
    • 2017-01-02
    相关资源
    最近更新 更多