平坦化mongoDB模式

平坦化mongoDB模式,mongodb,schema,Mongodb,Schema,我有一个现有的深度嵌套的mongoDB模式,我必须将其展平,因为我有一个复杂的查询,无法使用当前结构高效地进行查询。以下是模式的MWE: db.test.insert({ "_id" : ObjectId("58e574a768afb6085ec3a388"), "details" : [ { "_id" : ObjectId("58e55f0f68afb6085ec3a2cc"), "a"

我有一个现有的深度嵌套的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”:[{…}],
。但是,它现在已更新。

新的响应 打印数据

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 = [];
}
更新数据

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"),
            "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
“a”
或a
“b”
等,并且在将新键添加到
“details”
文档的顶层时,始终分配与该键匹配的最后找到的值

如果实际情况有所不同,则需要修改其中的各种
.forEach()
循环,以将“index”用作参数,并将该索引值作为键名的一部分。i、 e:

"a0Unit": "08",
"a0Size": "05",
"a1Unit": "09",
"a1Size": "06"
但这是一个细节,如果有必要的话,你必须计算出来,因为这与问题中数据的呈现方式不同

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

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的每个数组成员文档中的
\u id
字段中。因此,无论您做什么,都不要尝试使用mongoose本身运行代码。这是对数据的“一次性”批量更新,应该直接从shell运行。当然,您需要修改模式以适应新的结构

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

New Response 打印数据

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 = [];
}
更新数据

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"),
            "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
“a”
或a
“b”
等,并且在将新键添加到
“details”
文档的顶层时,始终分配与该键匹配的最后找到的值

如果实际情况有所不同,则需要修改其中的各种
.forEach()
循环,以将“index”用作参数,并将该索引值作为键名的一部分。i、 e:

"a0Unit": "08",
"a0Size": "05",
"a1Unit": "09",
"a1Size": "06"
但这是一个细节,如果有必要的话,你必须计算出来,因为这与问题中数据的呈现方式不同

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

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的每个数组成员文档中的
\u id
字段中。因此,无论您做什么,都不要尝试使用mongoose本身运行代码。这是对数据的“一次性”批量更新,应该直接从shell运行。当然,您需要修改模式以适应新的结构

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

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
因此,需要的另一个聚合阶段来对details.b、details.c和details.d执行类似的步骤。

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

因此,需要的另一个聚合阶段来执行详细信息的类似步骤。b、details.c和details.d。

您是否尝试过聚合框架中的
$project
$group
$unwind
功能?是否尝试“更新”将数据添加到新表单中,或者您是否尝试仅作为查询返回与新表单类似的结果?您是否尝试了聚合框架中的
$project
$group
$unwind
函数?是否尝试“更新”将数据添加到新表单中,或者您试图返回一个看起来像新表单的结果,只是作为一个查询?虽然您没有尝试使用它,
$facet
在这里引用完全是错误的,主要是因为它的函数和输出始终是一个“si”