数据库设计缺乏类似连接的查询在MongoDB中:如何使用refs来处理其他集合中的文档

时间:2017-06-16 14:29:25

标签: mongodb join rdbms database

我正在干预MongoDB,看看它如何在一个真实的项目中作为一个严肃的数据存储平台,但我对理解这个概念有点问题,或者更确切地说,数据设计应该是什么样子。我理解嵌入对象的想法,例如代替你在RDBMS中做的事情,比如:

PEOPLE (Table):
id | Name
---------
 1 | John
 2 | Steve

PHONES (Table):
id | peopleId | phone
 1 |    1     | 555 66 77
 2 |    1     | 555 66 78
 3 |    2     | 555 11 22

在MongoDB中,您将在嵌入详细信息对象的集合中创建两个文档,例如:

{
  name: "John",
  phones: [
    {phone: "555 66 77"},
    {phone: "555 66 78"}
  ]
},
{
  name: "Steve",
  phones: [
    {phone: "555 11 22"}
  ]
}

现在这种方法一切都很好,并且可以在每个主文档中细节对象相当独特的情况下工作(因为每个手机只属于一个人),但是一旦你进入(是!)关系的领域,其中细节对于他们的主人来说并非严格独特,你会遇到麻烦。 Cosider书籍/作者关系。有很多书,可能有一个以上的作者,而许多作者将有不止一本书(多对多的关系)。如果您将作者文档嵌入书籍中,则必须与作者书籍一样多次复制作者数据。相反,如果您将书籍嵌入到作者中,您将拥有与该书作者相同的重复数据,即同一本书将出现在其他作者文档中。毋庸置疑,这会造成数据整合问题。

{
  book: "A Nice Title",
  authors: [
    {name: "Jane", age: 30},
    {name: "Tom", age: 20}
  ]
},
{
  book: "Some Other Nice Title",
  authors: [
    {name: "Jane", age: 29},
    {name: "Tom", age: 21}
  ]
}

像这里一样,是简30岁还是29岁?

现在,正如我理解阅读here,解决此问题的首选方法是将子文档保留在自己的集合中并使用其_id而不是嵌入它(任何人都觉得我们已经回来了到目前为止的RDBMS?),通过手动操作并查询每个文档获取的详细信息(导致每个文档的许多查询,如果有文档列表,则将其与文档计数相乘!)或者使用DBRef,据说在DRIVER级别执行完全相同的操作,而不是在服务器级别,这意味着完全相同,只是我没有完成,而是由驱动程序完成,所以相同的查询号码适用,即:网络开销,服务器开销,等待,等待...这是一个例子:

people:
{
  _id: 1,
  name: "John",
  phones: [
    {phones_id: 1},
    {phones_id: 2}
  ]
},
{
  _id: 2,
  name: "Steve",
  phones: [
    {phone_id: 3}
  ]
}

电话:

{
  _id: 1,
  phone: "555 66 77"
},
{
  _id: 2,
  phone: "555 66 78"
},
{
  _id: 3,
  phone: "555 11 22"
}

这意味着在我获得人员文件的第一个查询之后,我将不得不通过电话收集再进行3次查询,以获取实际电话以生成人员电话号码列表。

在今天的数据加载中,我可以告诉你:不会飞。想象一下,这是一本 50.000 长书单,每本书有10位作者?我没有向服务器发送 500.001 查询,只是为了获得一个列表。

构造如:

bookLinks:
{ bookId: 1, authorId: 1}, {bookId: 2, authorId: 1}...

只会让事情变得更糟:现在你必须对链接进行一次查询,将相同数量的查询作为结果文档将它们链接到书籍,然后对作者进行一些查询,从而产生550.001个查询(50000本书)每个都有10位作者。

所以......因为任何真正的项目显然都有嵌入式(电话簿)和非嵌入式(作者/书籍)模型,并且由于MongoDB无法在服务器级别将dbrefs解析为其他集合中的文档并嵌入它们,是要走的路?在这种情况下设计文档集合的正确或首选方法是什么?

我希望我能够准确地描述我的疑虑。

注意:请不要建议本地缓存详细信息以减少查询次数(否:我仍然不会向服务器发送300.001查询而不是500.001)。这样的方法可能是设计不佳的补丁,但它们无法解决问题。

1 个答案:

答案 0 :(得分:1)

在某些情况下,您可以使用汇总管道和its $lookup operator来完成此操作。像这样的东西(抱歉,doc示例,不是你的例子)

db.orders.aggregate([
    {
      $lookup:
        {
          from: "inventory",
          localField: "item",
          foreignField: "sku",
          as: "inventory_docs"
        }
   }
])

基本上,这是一个左外连接,需要付出代价(更复杂的查询语法)。在您的情况下,您可能必须首先$unwind数组。

  

另外,你可以使用多次查找吗?

是的,它应该像在管道中放置几个​​$lookup步骤一样简单。