MongoDB多对多关联

时间:2010-02-25 18:46:47

标签: many-to-many associations mongodb

您如何与MongoDB进行多对多关联?

例如;假设您有一个Users表和一个Roles表。用户有很多角色,角色有很多用户。在SQL域中,您将创建一个UserRoles表。

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

MongoDB中如何处理相同类型的关系?

5 个答案:

答案 0 :(得分:82)

根据您的查询需求,您可以将所有内容放在用户文档中:

{name:"Joe"
,roles:["Admin","User","Engineer"]
}

要获得所有工程师,请使用:

db.things.find( { roles : "Engineer" } );

如果要在单独的文档中维护角色,则可以在roles数组中包含文档的_id而不是名称:

{name:"Joe"
,roles:["4b5783300334000000000aa9","5783300334000000000aa943","6c6793300334001000000006"]
}

并设置以下角色:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}

答案 1 :(得分:28)

我没有尝试根据我们多年使用RDBMS的经验进行建模,而是通过优化读取用例来使用MongoDB,Redis和其他NoSQL数据存储来更轻松地建模文档存储库解决方案,同时考虑周全写入用例需要支持的原子写操作。

例如,“角色中的用户”域名的使用如下:

  1. 角色 - 创建,读取,更新,删除,列出用户,添加用户,删除用户,清除所有用户,用户索引或类似内容以支持“用户在角色中”(操作如容器+自己的元数据)。
  2. 用户 - 创建,读取,更新,删除(CRUD操作,如独立实体)
  3. 可以将其建模为以下文档模板:

    User: { _id: UniqueId, name: string, roles: string[] }
        Indexes: unique: [ name ]
    Role: { _id: UniqueId, name: string, users: string[] }
        Indexes: unique: [ name ]
    

    为了支持高频用途,例如User实体中与角色相关的功能,User.Roles被有意地非规范化,存储在用户以及具有重复存储的Role.Users上。

    如果在文本中不是很明显,但这是使用文档存储库时鼓励的思维方式。

    我希望这有助于缩小与操作读取方面的差距。

    对于写入方,鼓励的是根据原子写入进行建模。例如,如果文档结构需要获取锁,更新一个文档,然后更新另一个文档,可能还有更多文档,然后释放锁,则模型可能失败。仅仅因为我们可以构建分布式锁并不意味着我们应该使用它们。

    对于角色中的用户模型,扩展我们的原子写入避免锁定的操作是在角色中添加或删除用户。在任何一种情况下,成功的操作都会导致单个用户和单个Role文档被更新。如果出现故障,很容易进行清理。这就是工作单元模式在使用文档存储库时出现的原因之一。

    真正延伸我们的原子写入避免锁定的操作是清除一个角色,这将导致许多用户更新从User.roles数组中删除Role.name。通常不鼓励这种清除操作,但如果需要可以通过订购操作来实现:

    1. 从Role.users获取用户名列表。
    2. 从步骤1中迭代用户名,从User.roles中删除角色名称。
    3. 清除Role.users。
    4. 如果出现问题(最有可能在步骤2中发生),则回滚很容易,因为步骤1中的同一组用户名可用于恢复或继续。

答案 2 :(得分:13)

我刚刚偶然发现了这个问题,虽然这是一个旧问题,但我认为在答案中添加一些未提及的可能性会很有用。此外,在过去几年中,事情已经发生了一些变化,所以值得强调的是SQL和NoSQL正在相互靠近。

其中一位评论者提出了明智的警示态度,即“如果数据是关系的,则使用关系”。但是,该注释仅在关系世界中才有意义,其中模式总是在应用程序之前出现。

关系世界:结构数据>编写应用程序以获取它
NOSQL WORLD:设计应用程序>相应地构建数据

即使数据是关系型的,NoSQL仍然是一种选择。例如,一对多关系完全没有问题,并且在MongoDB docs

中得到广泛报道

2015年问题的解决方案

自从发布此问题以来,已经有一些认真尝试将noSQL更接近SQL。由加利福尼亚大学(圣地亚哥分校)的Yannis Papakonstantinou领导的团队一直致力于FORWARD,这是SQL ++的一个实现,很快就可以解决持久性问题,例如这里发布的问题。

在更实际的层面上,Couchbase 4.0的发布意味着,您可以第一次在NoSQL中进行原生JOIN。他们使用自己的N1QL。这是来自tutorialsJOIN的示例:

SELECT usr.personal_details, orders 
        FROM users_with_orders usr 
            USE KEYS "Elinor_33313792" 
                JOIN orders_with_users orders 
                    ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END

N1QL允许大多数(如果不是全部)SQL操作,包括聚合,过滤等。

不那么新的混合溶液

如果MongoDB仍然是唯一的选择,那么我想回到我的观点,即应用程序应该优先于数据结构。没有一个答案提到混合嵌入,大多数查询数据都嵌入在文档/对象中,并且少数情况下会保留引用。

示例:信息(角色名称除外)可以等待吗?可以通过不请求用户不需要的任何内容来更快地启动应用程序吗?

如果用户登录并且他/她需要查看他/她所属的所有角色的所有选项,则可能是这种情况。但是,用户是“工程师”,很少使用此角色的选项。这意味着应用程序只需要显示工程师的选项,以防他/她想要点击它们。

这可以通过一个文档来实现,该文档在开始时告诉应用程序(1)用户所属的角色,以及(2)从哪里获取与特定角色相关联的事件的信息。

   {_id: ObjectID(),
    roles: [[“Engineer”, “ObjectId()”],
            [“Administrator”, “ObjectId()”]]
   }

或者,更好的是,索引roles集合中的role.name字段,您也可能不需要嵌入ObjectID()。

另一个例子:是关于所有时间请求的所有角色的信息吗?

也可能是用户登录仪表板并且90%的时间执行链接到“工程师”角色的任务。可以完全针对该特定角色进行混合嵌入,并仅为其余角色保留参考。

{_id: ObjectID(),
  roles: [{name: “Engineer”, 
           property1: value1,
           property2: value2
          },   
          [“Administrator”, “ObjectId()”]
         ]
}

无模式不仅仅是NoSQL的一个特征,在这种情况下它可能是一个优势。在用户对象的“角色”属性中嵌套不同类型的对象是完全有效的。

答案 3 :(得分:4)

如果员工和公司是entity-object 尝试使用以下架构:

employee{
   //put your contract to employee
   contracts:{ item1, item2, item3,...}
}

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}

答案 4 :(得分:1)

有两种方法可以使用:

第一种方法

在用户文档角色列表(数组)中添加参考链接:

{
  '_id': ObjectId('312xczc324vdfd4353ds4r32')
  user:faizanfareed,
  roles : [
           {'roleName':'admin', # remove this because when we will be updating some roles name we also need to be update in each user document. If not then ignore this.
             roleId: ObjectID('casd324vfdg65765745435v')
          },
            {'roleName':'engineer',
           roleId: ObjectID('casd324vfdvxcv7454rtr35vvvvbre')
           },
          ]
}

并且(根据查询要求)我们还可以将用户引用 id 添加到角色文档用户列表(数组)中:

{
  roleName:admin,
  users : [{userId: ObjectId('312xczc324vdfd4353ds4r32')}, .......]
}

但是将用户 id 添加到角色文档大小会超过16MB,这一点都不好。如果不超过角色文档大小且用户大小有界,我们可以使用这种方法。如果不需要,我们可以仅将角色 ID 添加到用户文档中。


第二种传统方法

创建新集合,其中每个文档都包含用户和角色的 ID。

{
  '_id': ObjectId('mnvctcyu8678hjygtuyoe')
  userId: ObjectId('312xczc324vdfd4353ds4r32')
  roleId: ObjectID('casd324vfdg65765745435v')
            
}

这种方法不会超过文档大小,但读取操作并不容易。


根据需求采用第一种或第二种方法。

对此的最终评论:采用第一种方法,只将 roleId 添加到用户文档数组中,因为没有任何角色不会大于用户。用户文档大小不会超过 16MB。