【问题标题】:Flattening mongoDB schema扁平化 mongoDB 模式
【发布时间】:2017-10-31 14:39:38
【问题描述】:

我有一个现有的深度嵌套的 mongoDB 模式,我必须将其展平,因为我有一个复杂的查询,无法使用当前结构有效地进行。这是架构的 MWE:

db.test.insert({
    "_id" : ObjectId("58e574a768afb6085ec3a388"),
    "details" : [
            {
                "_id" : ObjectId("58e55f0f68afb6085ec3a2cc"),
                "a" : [
                    {
                        "unit" : "08",
                        "size" : "5",
                        "pos" : "Far",
                        "_id" : ObjectId("58e55f0f68afb6085ec3a2d0")
                    }
                ],
                "b" : [
                    {
                        "unit" : "08",
                        "size" : "5",
                        "pos" : "Far",
                        "_id" : ObjectId("58e55f0f68afb6085ec3a2cd")
                    }
                ],
                "c" : [
                    {
                        "unit" : "08",
                        "size" : "3",
                        "pos" : "Far",
                        "_id" : ObjectId("58e55f0f68afb6085ec3a2ce")
                    }
                ],
                "d" : [
                    {
                        "unit" : "08",
                        "size" : "5",
                        "pos" : "Far",
                        "_id" : ObjectId("58e55f0f68afb6085ec3a2cf")
                    }
                ]
            }
        ]
    })

我想展平架构。想要的结果是这样的:

"_id" : ObjectId("58e574a768afb6085ec3a388"),
"tests" : [
        {
            "_id" : ObjectId("58e542fb68afb6085ec3a1d2"),
            "aUnit" : "08",
            "aSize" : "5",
            "aPos" : "Far",
            "bPos" : "Far",
            "bSize" : "5",
            "bUnit" : "08",
            "cPos" : "Far",
            "cSize" : "3",
            "cUnit" : "08",
            "dPos" : "Far",
            "dSize" : "5",
            "dUnit" : "08"
                }
            ]

我愿意一次一个地输入每种条目类型,我认为我有一种方法可以做到这一点,但它不起作用。这是我尝试过的:

db.test.find({"tests.$.details.a.unit":{$exists:true}}).forEach(function(doc) {      
    doc.tests = {aUnit:tests.details.a.unit};
    delete tests.details.a.unit;
    db.test.save(doc);
    });

但是,这并没有改变。如何改进我的查询以扁平化我的架构?

已编辑:我意识到 MWE 与我打算使用它的那个相比有一个小错误。我正在关闭每个条目。例如,"a" : [{ ... }], 被错误地写为{"a" : [{ ... }]},。但是,它现在已更新。

【问题讨论】:

  • 您是否尝试过属于聚合框架的$project $group$unwind 函数?
  • 您是在尝试将数据“更新”到您的新表单中,还是仅仅作为查询返回一个看起来像您的新表单的结果?

标签: mongodb schema


【解决方案1】:

新回复

打印数据

db.test.find().forEach(doc => {
  doc.details = doc.details.map( detail => {
    Object.keys(detail).filter( k => k !== "_id" ).forEach( k => {
      detail[k].forEach( item => {
        Object.keys(item).filter(i => i !== "_id" ).forEach( inner => {
          detail[k + inner.charAt(0).toUpperCase() + inner.substr(1)]
            = item[inner];
        })
      });
      delete detail[k];
    });
    return detail;
  });
  printjson(doc);
});

更新数据

db.test.find().forEach(doc => {
  doc.details = doc.details.map( detail => {
    Object.keys(detail).filter( k => k !== "_id" ).forEach( k => {
      detail[k].forEach( item => {
        Object.keys(item).filter(i => i !== "_id" ).forEach( inner => {
          detail[k + inner.charAt(0).toUpperCase() + inner.substr(1)]
            = item[inner];
        })
      });
      delete detail[k];
    });
    return detail;
  });

  ops = [
    ...ops,
    { "updateOne": {
      "filter": { "_id": doc._id },
      "update": { "$set": { "doc.details": doc.details } }
    }}
  ];

  if ( ops.length >= 500 ) {
    db.test.bulkWrite(ops);
    ops = [];
  }
});

if ( ops.length > 0 ) {
  db.test.bulkWrite(ops);
  ops = [];
}

输出表格

{
    "_id" : ObjectId("58e574a768afb6085ec3a388"),
    "details" : [
        {
          "_id" : ObjectId("58e55f0f68afb6085ec3a2cc"),
          "aUnit" : "08",
          "aSize" : "5",
          "aPos" : "Far",
          "bUnit" : "08",
          "bSize" : "5",
          "bPos" : "Far",
          "cUnit" : "08",
          "cSize" : "3",
          "cPos" : "Far",
          "dUnit" : "08",
          "dSize" : "5",
          "dPos" : "Far"
        }
    ]
}

原始数据

{
    "_id" : ObjectId("58e574a768afb6085ec3a388"),
    "tests" : [
      {
        "_id" : ObjectId("58e542fb68afb6085ec3a1d2"),
        "details" : [
          {
            "a" : [
              {
                "unit" : "08",
                "size" : "5",
                "pos" : "Far",
                "_id" : ObjectId("58e542fb68afb6085ec3a1d6")
              }
            ]
          },
          {
            "b" : [
              {
                "pos" : "Drive Side Far",
                "size" : "5",
                "unit" : "08",
                "_id" : ObjectId("58e542fb68afb6085ec3a1d3")
              }
            ]
          },
          {
            "c" : [
              {
                "pos" : "Far",
                "size" : "3",
                "unit" : "08",
                "_id" : ObjectId("58e542fb68afb6085ec3a1d4")
              }
            ]
          },
          {
            "d" : [
              {
                "pos" : "Far",
                "size" : "5",
                "unit" : "08",
                "_id" : ObjectId("58e542fb68afb6085ec3a1d5")
              }
            ]
          }
        ]
      }
    ]
}

原答案

如果您尝试“更新”您的数据,那么它所涉及的内容比您尝试的要多得多。您有多个数组,您需要实际“遍历”数组元素,而不是尝试直接访问它们。

这里只是一个“打印”“扁平化”数据的示例:

db.test.find().forEach(doc => {
  doc.tests = doc.tests.map( test => {
    test.details.forEach( detail => {
      Object.keys(detail).forEach( key => {
        detail[key].forEach( item => {
          Object.keys(item).forEach( inner => {
            if ( inner !== '_id' ) {
              test[key + inner.charAt(0).toUpperCase() + inner.substr(1)]
                = item[inner];
            }
          });
        });
      });
    });
    delete test.details;
    return test;
  });
  printjson(doc);
})

我相信这可以提供您正在寻找的结构:

{
    "_id" : ObjectId("58e574a768afb6085ec3a388"),
    "tests" : [
        {
            "_id" : ObjectId("58e542fb68afb6085ec3a1d2"),
            "aUnit" : "08",
            "aSize" : "5",
            "aPos" : "Far",
            "bPos" : "Drive Side Far",
            "bSize" : "5",
            "bUnit" : "08",
            "cPos" : "Far",
            "cSize" : "3",
            "cUnit" : "08",
            "dPos" : "Far",
            "dSize" : "5",
            "dUnit" : "08"
        }
    ]

}

现在我没有考虑到在您的"details" 数组中带有"a" 等键的文档可能会出现多次的任何可能性。所以我只是在考虑里面只有一个文档有一个"a" 或一个"b" 等,并且在将新键添加到顶层时总是分配与该键匹配的最后一个值"details" 文件。

如果您的实际情况有所不同,那么您需要修改其中的各种.forEach() 循环,以便也使用“索引”作为参数并将该索引值作为键名的一部分。即:

"a0Unit": "08",
"a0Size": "05",
"a1Unit": "09",
"a1Size": "06"

但这是一个您必须在必要时解决的细节,因为这与问题中数据的呈现方式不同。

但是,如果这非常适合您要更新的内容,则只需使用定期执行的 .bulkWrite() 语句运行循环:

let ops = [];

db.test.find().forEach(doc => {
  doc.tests = doc.tests.map( test => {
    test.details.forEach( detail => {
      Object.keys(detail).forEach( key => {
        detail[key].forEach( item => {
          Object.keys(item).forEach( inner => {
            if ( inner !== '_id' ) {
              test[key + inner.charAt(0).toUpperCase() + inner.substr(1)]
                = item[inner];
            }
          });
        });
      });
    });
    delete test.details;
    return test;
  });

  ops = [
    ...ops,
    { "updateOne": {
      "filter": { "_id": doc._id },
      "update": { "$set": { "tests": doc.tests } }
    }}
  ];

  if ( ops.length >= 500 ) {
    db.test.bulkWrite(ops);
    ops = [];
  }
});

if ( ops.length > 0 ) {
  db.test.bulkWrite(ops);
  ops = [];
}

它也出现在您正在使用 mongoose 的每个数组成员文档中的 _id 字段中。因此,无论您做什么,都不要尝试使用 mongoose 本身运行代码。这是对数据的“一次性”批量更新,应直接从 shell 运行。然后,您当然需要修改架构以适应新结构。

但这就是为什么您应该首先使用printjson() 方法在shell 中运行您的数据。

【讨论】:

  • 我尝试使用它,但是我遇到了一个语法错误,导致我无法对其进行测试。我在外壳中运行它。 SyntaxError: missing : after property id @(shell):3:8
  • @black_sheep07 哎呀。认为我在删除部分方面有点过分热心。仅打印的第一个列表应该可以完美运行,并且只是修复了缺少行的更新列表。我只是将清单直接粘贴到我的 shell 中并更新了您提供的相同文档,如图所示,
  • 谢谢。如果新事物的名称由单词组成,而不仅仅是一个字母(但仍然只出现一次)怎么办?例如,不是a变成aUnitb变成bUnit,我有apple变成appleUnitbanana变成bananaUnit
  • @black_sheep07 这里的列表并不关心这个。它只需要获取密钥中的任何内容并使用它。就像我说的,我们只能回答您的问题,并且建议您在提交其他更新之前使用“打印”表格,并且您的实际结构可能需要更改列表。它仍然应该是一个很好的基础,但是只要文档遵循相同的文档和嵌套的数组结构,它应该可以正常工作。
  • 我在尝试运行代码时遇到了失败。 TypeError: detail[key].forEach is not a function。有什么原因吗?
【解决方案2】:

以下

db.collection.aggregate(
    [{$unwind:"$tests"},
    {$unwind:"$tests.details"},
    {$unwind:"$tests.details.a"},
    {$group:{
        _id:"$_id",
        "tests": {"$push":{
            "aPos":"$tests.details.a.pos",
            "aSize":"$tests.details.a.size",
            "aUnit":"$tests.details.a.unit"
        }}}},
    ])

产生:

{ "_id" : ObjectId("58e574a768afb6085ec3a388"), "tests" : [ { "aPos" : "Far", "aSize" : "5", "aUnit" : "08" } ] }

上面只产生了一组字段:值对;在同一级别执行多个 $unwind 不起作用:

db.collection.aggregate(
    [{$unwind:"$tests"},
    {$unwind:"$tests.details"},
    {$unwind:"$tests.details.a"},
    {$unwind:"$tests.details.b"},
    {$group:{
        _id:"$_id",
        "tests": {"$push":{
            "aPos":"$tests.details.a.pos",
            "aSize":"$tests.details.a.size",
            "aUnit":"$tests.details.a.unit",
            "bPos":"$tests.details.b.pos",
            "bSize":"$tests.details.b.size",
            "bUnit":"$tests.details.b.unit"
        }}}},
    ])  //does not run

因此,需要有$facet的另一个聚合阶段来对details.b、details.c和details.d进行类似的步骤。

【讨论】:

  • 虽然您没有尝试使用它,但$facet 在这里引用是完全错误的,主要是因为它的功能和输出是始终“单个文档",所以是一个明确的 BSON 限制断路器。尝试的问题当然是您需要使用“特定路径”,这可能与所有文档不一致。可以对所需结果进行聚合,但它会“灵活地”需要使用 $objectToArray$arrayToObject 来重命名键。它也确实很笨拙。除了问题提到“更新”。客户端代码要简单得多。
猜你喜欢
  • 2019-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多