【问题标题】:MongoDB/CouchDB: Join a table to itselfMongoDB/CouchDB:将表加入自身
【发布时间】:2011-09-04 11:44:51
【问题描述】:

我有一个关系数据模型,我正在考虑迁移到 MongoDB 或 CouchDB,并且我正在尝试了解查询的工作方式。假设我有两个实体,Employees 和 Projects,以及一个名为 Assignments 的多对多连接表。我想查询两个用户合作的所有项目。在 SQL 中,我可以这样做:

SELECT DISTINCT a1.project_id
FROM assignments a1, assignments a2
WHERE a1.project_id = a2.project_id
AND a1.employee_id = ?
AND a2.employee_id = ?

假设我有 Employee、Project 和 Assignment “文档”,我将如何在 NoSQL 中执行此操作?或者您会以不同的方式构建文档,这将如何影响查询?

我很高兴听到有关 Mongo 的查询 API 和 Couch 的 map/reduce 方法的答案。

【问题讨论】:

    标签: mongodb couchdb nosql


    【解决方案1】:

    您正在寻找的实际上是 N:M 映射。在这种情况下,您尝试将表映射到自身,但这与尝试将“员工”映射到“项目”没有太大区别。

    有一个很长的答案over here,所以我不会在这里重复。

    在您的具体情况下,我认为您需要稍微重新设计您的数据。您有一个包含两个数据点的assignments 表:projectIDemployeedID。这是将 N 个项目加入 M 个员工的经典表格。

    在 MongoDB 中,您通常根本没有这个“表”。如果您想将员工分配给项目,您可以在项目本身中存储一个 employeeID 数组。

    { name: 'projectx', emps: [ 1, 2, 3] }
    { name: 'projecty', emps: [ 3, 2] }
    

    看起来您的查询基本上是“查找员工 2 和 3 一起工作的所有项目”。 MongoDB 有一个 $all query operator 可以使用上述结构为您执行此操作。

    请注意,这只是一个查询。在进行架构设计时,查看整个系统并确定所有重要查询非常重要。

    【讨论】:

      【解决方案2】:

      在 MongoDB 中,您可以这样做。我使用交互式 JavaScript Shell。

      首先创建一些用户:

      > db.so.employee.insert({name: "Joe"})
      > db.so.employee.insert({name: "Moe"})
      > db.so.employee.insert({name: "Bart"})
      > db.so.employee.insert({name: "Homer"})
      > db.so.employee.find()
      { "_id" : ObjectId("4de35ccbcc0379536e1ac43b"), "name" : "Joe" }
      { "_id" : ObjectId("4de35ccfcc0379536e1ac43c"), "name" : "Moe" }
      { "_id" : ObjectId("4de35cd3cc0379536e1ac43d"), "name" : "Bart" }
      { "_id" : ObjectId("4de35cd7cc0379536e1ac43e"), "name" : "Homer" }
      

      现在创建一些项目

      > db.so.project.insert({name: "Web App A"})
      > db.so.project.insert({name: "Web App B"})
      > db.so.project.insert({name: "Web App C"})
      > db.so.project.find();
      { "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }
      { "_id" : ObjectId("4de35d13cc0379536e1ac440"), "name" : "Web App B" }
      { "_id" : ObjectId("4de35d15cc0379536e1ac441"), "name" : "Web App C" }
      

      将用户添加到项目中

      > db.so.project.update({name: "Web App A"}, {$push: {employees: ObjectId('4de35ccbcc0379536e1ac43b') }})
      > db.so.project.update({name: "Web App A"}, {$push: {employees: ObjectId('4de35ccfcc0379536e1ac43c') }})
      > db.so.project.update({name: "Web App B"}, {$push: {employees: ObjectId('4de35ccfcc0379536e1ac43c') }})
      > db.so.project.update({name: "Web App C"}, {$push: {employees: ObjectId('4de35ccfcc0379536e1ac43c') }})
      > db.so.project.update({name: "Web App B"}, {$push: {employees: ObjectId('4de35cd3cc0379536e1ac43d') }})
      > db.so.project.update({name: "Web App C"}, {$push: {employees: ObjectId('4de35cd3cc0379536e1ac43d') }})
      > db.so.project.update({name: "Web App B"}, {$push: {employees: ObjectId('4de35cd7cc0379536e1ac43e') }})
      
      > db.so.project.find()
      { "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "employees" : [
          ObjectId("4de35ccbcc0379536e1ac43b"),
          ObjectId("4de35ccfcc0379536e1ac43c")
      ], "name" : "Web App A" }
      { "_id" : ObjectId("4de35d15cc0379536e1ac441"), "employees" : [
          ObjectId("4de35ccfcc0379536e1ac43c"),
          ObjectId("4de35cd3cc0379536e1ac43d")
      ], "name" : "Web App C" }
      { "_id" : ObjectId("4de35d13cc0379536e1ac440"), "employees" : [
          ObjectId("4de35cd3cc0379536e1ac43d"),
          ObjectId("4de35cd7cc0379536e1ac43e")
      ], "name" : "Web App B" }
      

      如果您现在想查找“乔”所从事的所有项目

      > db.so.project.find({employees: ObjectId('4de35ccbcc0379536e1ac43b') }, {name: 1})
      { "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }
      

      查找 Joe OR Moe 从事的所有项目

      > db.so.project.find({employees: {$in: [ObjectId('4de35ccbcc0379536e1ac43b'), ObjectId('4de35ccfcc0379536e1ac43c')] }}, {name: 1})
      { "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }
      { "_id" : ObjectId("4de35d15cc0379536e1ac441"), "name" : "Web App C" }
      { "_id" : ObjectId("4de35d13cc0379536e1ac440"), "name" : "Web App B" }
      

      查找 Joe AND moe 从事的所有项目

      > db.so.project.find({employees: {$all: [ObjectId('4de35ccbcc0379536e1ac43b'), ObjectId('4de35ccfcc0379536e1ac43c')] }}, {name: 1})
      { "_id" : ObjectId("4de35d0fcc0379536e1ac43f"), "name" : "Web App A" }
      

      要获得特定项目的所有员工,您需要两个查询。发现 项目 C 的所有员工。

      > db.so.project.find({name: "Web App C"}, {employees: 1})
      { "_id" : ObjectId("4de35d15cc0379536e1ac441"), "employees" : [
          ObjectId("4de35ccfcc0379536e1ac43c"),
          ObjectId("4de35cd3cc0379536e1ac43d")
      ] }
      

      在您的应用程序中,您可以从返回值和 构建这个查询:

      > db.so.employee.find({_id: {$in: [ObjectId('4de35ccfcc0379536e1ac43c'), ObjectId('4de35cd3cc0379536e1ac43d')] }})
      { "_id" : ObjectId("4de35ccfcc0379536e1ac43c"), "name" : "Moe" }
      { "_id" : ObjectId("4de35cd3cc0379536e1ac43d"), "name" : "Bart" }
      

      我希望这有助于理解 MongoDB 的工作原理以及如何建立关系。我在这里使用了手动取消引用。这意味着我直接保存 ObjectID 并手动获取它。还存在“DBRef”,然后您的驱动程序会为您获取它。

      【讨论】:

      • 有没有办法将项目查询中的员工“加入”,这样就可以将最后两个查询合二为一了?
      • 不,MongoDB 没有“连接”,这是一个“功能”。您需要在客户端上执行此操作。在您的软件中。
      • 优秀的cmets Sid,真的很有帮助
      【解决方案3】:

      我只能在沙发上分享风景,我,嗯,每隔一段时间就会放松一下。首先,在处理基于文档的系统时,您几乎总是必须有意忘记 SQL。

      其次,您可以选择更改结构并将不需要连接的实体数量减少到最低限度,或者您可以使用将不同类型的文档组合成单个结果集的视图。

      前者(重新设计)是首选方法,因为连接本质上是 nosql 的一种外来方法,因为规范化不是那里的要求。基于文档的不是关系型的。

      【讨论】:

      • 好的,感谢您的回复。但是我很难找出一个可以让我查询这种结构的文档模型。有什么想法吗?
      • 我相信您必须选择主要实体,我对此无能为力,因为我对您的应用程序一无所知。如果您以用户为中心并且项目相对简单,请将项目包装在用户中,反之亦然。如果用户和项目同样重要,请在项目/作业文档中包含部分用户数据,这样就足以显示您的连接示例获取的任何内容(缺点是如果一个文档发生更改,则需要更新两个文档)。最后,您可以在项目中包含用户 ID,这将是一个联接。
      • PS 分配很可能必须合并到用户或项目中,具体取决于您的应用程序。
      猜你喜欢
      • 1970-01-01
      • 2012-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-23
      • 1970-01-01
      相关资源
      最近更新 更多