如何使用新的id来mongoexport和mongoimport集合,但保持关系

时间:2018-05-21 16:35:06

标签: mongodb mongoimport mongoexport

我使用mongoexport导出2个集合:

mongoexport -h -u -p --db dev -c parent -q '{_id: ObjectId("5ae1b4b93b131f57b33b8d11")}' -o parent.json

mongoexport -h -u -p --db dev -c children -q '{parentId: ObjectId("5ae1b4b93b131f57b33b8d11")}' -o children.json

从第一个我得到一个记录(由ID选择),从第二个我得到许多记录(由parentId选择,它是第一个的ID。

如何使用mongoimport导入具有新ID但保持关系的那些 - > parent.id = child.parentId

1 个答案:

答案 0 :(得分:3)

TLDR;跳到这些部分来解决这个问题,或者如果你想澄清错误观念,请实际阅读。

背景

让我们澄清一下这里的基本误解,因为MongoDB &#34;本身&#34; 没有任何关系概念 < / strong>即可。 A&#34;关系&#34;因为你所指的只是一个集合中属性中记录的值,其中&#34;指的是#34; 到另一个集合中存在的另一个值。关于这些&#34;外键&#34; 的任何内容都没有强制执行任何类型的MongoDB约束,它定义数据&#34;相关&#34; ,它们只是&#34;值&#34;

一旦你接受了这个事实,那么为什么任何工具如mongoexport或甚至像find()这样的基本查询操作真的不知道为什么只是因为你在某个地方放了一个值是&#34;意味着&#34; 去从其他地方获取数据。

很久以前,有一个名为DBRef的概念(并且在较小范围内),它不仅仅存储了一个&#34;值&#34;还有一些细节,关于这个价值在&#34;参考&#34;术语。这可以是集合或数据库和集合。然而,这个概念仍然相对短暂,在现代背景下无用。即使有这个&#34;元数据&#34;存储,数据库本身没有涵盖&#34;关系&#34;对数据。检索仍然是一个客户概念&#34;一些司机有能力看到DBRef,然后&#34;解决&#34;它通过向服务器发出另一个查询来检索&#34;相关的&#34;数据

当然,这是&#34;充满漏洞&#34;这个概念被放弃了,有利于更现代的概念,特别是$lookup

现代建筑

这一切真正归结为MongoDB本身关注的是&#34;关系&#34;实际上是 &#34;客户概念&#34; ,在某种意义上说是一个&#34;外部实体&#34;实际上做出了决定&#34;这个&#34;与&#34;那是&#34;通过您定义的数据&#34;相等的参考&#34;。

没有&#34;数据库规则&#34;强制执行此操作,因此与各种传统的RDBMS解决方案相比,&#34;对象存储&#34; MongoDB的本质基本上是说&#34; ...那不是我的工作,委托给别人&#34; ,这通常意味着你在&#34;客户端&#中定义34;用于访问数据库的逻辑。

但是,&#34;某些工具&#34; 允许&#34;服务器&#34;真正按照这种逻辑行事。这些基本上围绕$lookup,这基本上是他现代的方法&#34;执行&#34;加入&#34;使用MongoDB时在服务器上。

因此,如果您有相关数据&#34;你想&#34;导出&#34;,那么你基本上有几个选择:

  • 创建&#34;视图&#34;使用$ lookup

    MongoDB在3.2版本中引入了"views"。这些基本上是#34;聚合管道&#34;其中&#34;伪装&#34; 作为集合。对于.find()甚至mongoexport等正常操作的所有目的,此管道看起来就像一个集合,可以这样访问。

    db.createView("related_view", "parent", [
      { "$lookup": {
        "from": "children",
        "localField": "_id",
        "foreignField": "parentId",
        "as": "children"
      }}
    ])
    

    用那个&#34;查看&#34;您可以使用为&#34;视图&#34;

    定义的名称来调用mongoexport
    mongoexport -c related_view -o output.json
    

    正如$lookup所做的那样,每个&#34;父母&#34; item现在将包含一个带有&#34;相关&#34;的数组。来自&#34;孩子的内容&#34;由外键。

    由于$lookup将输出生成为BSON文档,因此所有MongoDB都适用相同的约束条件,因为生成的&#34; join&#34;任何文件中都不能超过16MB。因此,如果数组导致父文档超出此限制,则使用输出作为&#34;数组&#34;嵌入在文档中的不是一种选择。

    对于这种情况,您通常会使用$unwind以便&#34;去标准化&#34;输出内容就像在典型的SQL连接中一样。在这种情况下,&#34;父母&#34;将为每个&#34;相关的&#34;复制文件。成员和输出文档与来自相关子项的文档匹配,但包含所有父信息和&#34; children&#34;作为一个独特的嵌入财产。

    这只是意味着将$unwind添加到这样的&#34;视图&#34;:

    db.createView("related_view", "parent", [
      { "$lookup": {
        "from": "children",
        "localField": "_id",
        "foreignField": "parentId",
        "as": "children"
      }},
      { "$unwind": "$children" }
    ])
    

    因为我们只是为每个相关的孩子输出基本上一个文件&#34;那么不太可能违反BSON限制。对于父母和孩子都有非常大的文件,这仍然是一种可能性,尽管很少见。对于那种情况,我们稍后会提到不同的处理方式。

  • 直接在脚本中使用$ lookup

    如果您没有MongoDB版本支持&#34;观看&#34;但你仍然有$lookup并没有限制BSON限制,你仍然可以基本上&#34;脚本&#34;使用mongo shell调用聚合管道并输出为JSON。

    这个过程类似,而不是使用&#34;视图&#34;和mongoexport,我们手动包装一些可以从命令行调用到shell中的命令:

    mongo --quiet --eval '
        db.parent.aggregate([ 
          { "$lookup": {
            "from": "children",
            "localField": "_id",
            "foreignField": "parentId",
            "as": "children"
          }}
        ]).forEach(p => printjson(p))'
    

    再次与您之前可以选择$unwind之前的流程相同,如果这就是您的目标

  • 编写&#34;加入&#34;

    如果你在没有$lookup支持的MongoDB实例上运行(你不应该,因为低于3.0没有更多的官方支持)或者你确实有一个场景,其中&#34;加入&# 34;将为每个父母创建数据&#34;超过BSON限制的文件,然后另一个选项是&#34;脚本&#34;整个连接过程通过执行查询来获得&#34;相关的&#34;数据并输出它。

    mongo --quiet --eval '
        db.parent.find().forEach(p =>
          printjson(
            Object.assign(p, {
            children: db.children.find({ parentId: p._id }).toArray()
            })
          )
        )'
    

    甚至在&#34; unwound&#34;或者&#34;去标准化&#34;形式:

    mongo --quiet --eval '
        db.parent.find().forEach(p =>
          db.children.find({ parentId: p._id }).forEach(child =>
            printjson(Object.assign(p,{ child }))
          )
        )'
    

摘要

底线是&#34; MongoDB本身&#34;我不知道&#34;关系&#34;,你真的应该提供这个细节。是以&#34;视图&#34;的形式出现的。您可以访问或通过其他方式定义&#34;代码&#34;必须明确说明&#34;术语&#34;这个&#34;关系&#34;,因为就数据库本身而言,这根本不存在于任何其他形式。

如果您打算在&#34; export&#34;只是为了创造一个新的集合&#34;然后或者简单地创建&#34;视图&#34;或使用$out聚合运算符:

db.parent.aggregate([
  { "$lookup": {
    "from": "children",
    "localField": "_id",
    "foreignField": "parentId",
    "as": "children"
  }},
  { "$out": "related_collection" }
])

如果你想改变父母&#34;用&#34;嵌入&#34;数据,然后循环并使用bulkWrite()

var batch = [];

db.parent.aggregate([
  { "$lookup": {
    "from": "children",
    "localField": "_id",
    "foreignField": "parentId",
    "as": "children"
  }}
]).forEach(p => {
  batch.push({
    "updateOne": {
      "filter": { "_id": p._id },
      "update": {
        "$push": { "children": { "$each": p.children } }
      }
    }
  });

  if (batch.length > 1000) {
    db.parent.bulkWrite(batch);
    batch = [];
  })
});

if (batch.length > 0) {
  db.parent.bulkWrite(batch);
  batch = [];
}

根本不需要&#34;导出&#34;仅仅是为了创建一个新的集合或改变现有的集合。当你想要真正保持集合为&#34;嵌入式&#34;时,你会这样做。数据并不需要每个请求的$lookup开销。但决定是否嵌入或引用&#34;是另一个故事。