Javascript MongoDB-仅当嵌套数组中的所有项存在时才更新它们

Javascript MongoDB-仅当嵌套数组中的所有项存在时才更新它们,javascript,node.js,mongodb,mongoose,nosql,Javascript,Node.js,Mongodb,Mongoose,Nosql,我有一个多级嵌套文档(它的动态和某些级别可能会丢失,但最多3个级别)。我想更新所有子级和子级路由(如果有)。这种情况与任何Windows资源管理器中的情况相同,在Windows资源管理器中,更改父文件夹路由时,所有子文件夹的路由都需要更改。例如,在下面的例子中,如果我在route==“l1/l2a”并且它的名称需要编辑为“l2c”,那么我将更新它的路由为route=“l1/l2c,并且我将更新所有孩子的路由为“l1/l2c/l3a” { “名称”:“l1”, “路线”:“l1”, “儿童”: [

我有一个多级嵌套文档(它的动态和某些级别可能会丢失,但最多3个级别)。我想更新所有子级和子级路由(如果有)。这种情况与任何Windows资源管理器中的情况相同,在Windows资源管理器中,更改父文件夹路由时,所有子文件夹的路由都需要更改。例如,在下面的例子中,如果我在
route==“l1/l2a”
并且它的名称需要编辑为“l2c”,那么我将更新它的路由为
route=“l1/l2c
,并且我将更新所有孩子的路由为
“l1/l2c/l3a”

{
“名称”:“l1”,
“路线”:“l1”,
“儿童”:
[
{
“名称”:“l2a”,
“路线”:“l1/l2a”,
“儿童”:
[
{
“名称”:“l3a”,
“路线”:“l1/l2a/l3a”
}]
},
{
“名称”:“l2b”,
“路线”:“l1/l2b”,
“儿童”:
[
{
“名称”:“l3b”,
“路线”:“l1/l2b/l3b”
}]
}
]
}
目前,我可以前往一个点,并可以通过以下方式更改其名称和路线:

router.put('/navlist',(req,res,next)=>{
newname=req.body.newName //suppose l2c
oldname=req.body.name //suppose l2a
route=req.body.route // existing route is l1/l2a
id=req.body._id


newroute=route.replace(oldname,newname); // l1/l2a has to be changed to l1/l2c
let segments = route.split('/');  
let query = { route: segments[0]};
let update, options = {};

let updatePath = "";
options.arrayFilters = [];
for(let i = 0; i < segments.length  -1; i++){
    updatePath += `children.$[child${i}].`;
    options.arrayFilters.push({ [`child${i}.route`]: segments.slice(0, i + 2).join('/') });
} //this is basically for the nested children

updateName=updatePath+'name'
updateRoute=updatePath+'route';

update = { $setOnInsert: { [updateName]:newDisplayName,[updateRoute]:newroute } };      
NavItems.updateOne(query,update, options)
 })
重要的是级别是可定制的,因此我不知道是否有“l3A”。就像可以有“l3A”,但可能没有“l3B”。但我的代码只需要每个正确的路径,否则它会给出一个错误

code 500 MongoError:要应用数组更新,文档中必须存在路径“children.1.children”。

因此,问题是如何使用$set将更改应用于实际存在的路径,以及如何编辑现有的路由部分。如果路径存在,它很好,如果路径不存在,我将收到错误。

您不能随心所欲。因为mongo不支持它。我可以提供您从mongo获取所需的项目。更新使用自定义递归函数help.And do
db.collection.updateOne(_id,{$set:data})


我认为对于第一级和第二级更新,
arrayFilted
是不可能的,但是对于第三级更新,它是可能的

可能的方法是,您可以从MongoDB 4.2开始使用

我只是建议一种方法,您可以根据自己的理解进一步简化,减少查询

使用
$map
迭代子数组循环,使用
$cond
检查条件,并使用
$mergeObjects
合并对象

let id = req.body._id;
let oldname = req.body.name;
let route = req.body.route;
let newname = req.body.newName;

let segments = route.split('/');
级别1更新:

第2级更新:

第3级更新:


为什么要为每个级别单独查询?
您可以执行单个查询,但只要您只需要更新单个级别数据或特定级别的数据,它就会更新所有级别的数据。我知道这是冗长的代码和查询,但我可以说这是查询操作的优化版本。

更新

db.collection.update({
  "_id": 1
},
{
  "$set": {
    "name": "m1",
    "route": "m1"
  },
  "$set": {
    "children.$[].route.0": "m1",
    "children.$[].children.$[].route.0": "m1"
  }
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].route.1": "m2a",
    "children.$[child].name": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a" }]
})


db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].children.$[].route.1": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a"}]
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[].children.$[child].name": "m3a"
    "children.$[].children.$[child].route.2": "m3a"
  }
},
{
  "arrayFilters":[{"child.name": "l3a"}]
})
使用引用时,您可以简化更新。更新/插入非常简单,因为您只能更新目标级别或插入新级别,而不必担心更新所有级别。让聚合负责填充所有级别并生成路由字段

工作示例-

结构

[
  {
    "_id": 1,
    "name": "l1",
    "children": [
      2,
      3
    ]
  },
  {
    "_id": 2,
    "name": "l2a",
    "children": [
      4
    ]
  },
  {
    "_id": 3,
    "name": "l2b",
    "children": [
      5
    ]
  },
  {
    "_id": 4,
    "name": "l3a",
    "children": []
  },
  {
    "_id": 5,
    "name": "l3b",
    "children": []
  }
]

插入查询

db.collection.insert({"_id": 4, "name": "l3a", "children": []}); // Inserting empty array simplifies aggregation query 
更新查询

db.collection.update({"_id": 4}, {"$set": "name": "l3c"});
聚集

db.collection.aggregate([
  {"$match":{"_id":1}},
  {"$lookup":{
    "from":"collection",
    "let":{"name":"$name","children":"$children"},
    "pipeline":[
      {"$match":{"$expr":{"$in":["$_id","$$children"]}}},
      {"$addFields":{"route":{"$concat":["$$name","/","$name"]}}},
      {"$lookup":{
        "from":"collection",
        "let":{"route":"$route","children":"$children"},
        "pipeline":[
          {"$match":{"$expr":{"$in":["$_id","$$children"]}}},
          {"$addFields":{"route":{"$concat":["$$route","/","$name"]}}}
        ],
        "as":"children"
      }}
    ],
    "as":"children"
  }}
])
原创

在将路由呈现给用户之前,您可以将其设置为数组类型和格式。这将大大简化更新。当嵌套级别不存在时,您必须将查询分解为多个更新(例如级别2更新)。可以使用事务以原子方式执行多个更新

差不多

[
  {
    "_id": 1,
    "name": "l1",
    "route": "l1",
    "children": [
      {
        "name": "l2a",
        "route": [
          "l1",
          "l2a"
        ],
        "children": [
          {
            "name": "l3a",
            "route": [
              "l1",
              "l2a",
              "l3a"
            ]
          }
        ]
      }
    ]
  }
]
级别1更新

db.collection.update({
  "_id": 1
},
{
  "$set": {
    "name": "m1",
    "route": "m1"
  },
  "$set": {
    "children.$[].route.0": "m1",
    "children.$[].children.$[].route.0": "m1"
  }
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].route.1": "m2a",
    "children.$[child].name": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a" }]
})


db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].children.$[].route.1": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a"}]
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[].children.$[child].name": "m3a"
    "children.$[].children.$[child].route.2": "m3a"
  }
},
{
  "arrayFilters":[{"child.name": "l3a"}]
})
第2级更新

db.collection.update({
  "_id": 1
},
{
  "$set": {
    "name": "m1",
    "route": "m1"
  },
  "$set": {
    "children.$[].route.0": "m1",
    "children.$[].children.$[].route.0": "m1"
  }
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].route.1": "m2a",
    "children.$[child].name": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a" }]
})


db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].children.$[].route.1": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a"}]
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[].children.$[child].name": "m3a"
    "children.$[].children.$[child].route.2": "m3a"
  }
},
{
  "arrayFilters":[{"child.name": "l3a"}]
})
第3级更新

db.collection.update({
  "_id": 1
},
{
  "$set": {
    "name": "m1",
    "route": "m1"
  },
  "$set": {
    "children.$[].route.0": "m1",
    "children.$[].children.$[].route.0": "m1"
  }
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].route.1": "m2a",
    "children.$[child].name": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a" }]
})


db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[child].children.$[].route.1": "m2a"
  }
},
{
  "arrayFilters":[{"child.name": "l2a"}]
})
db.collection.update({
  "_id": 1
},
{
  "$set": {
    "children.$[].children.$[child].name": "m3a"
    "children.$[].children.$[child].route.2": "m3a"
  }
},
{
  "arrayFilters":[{"child.name": "l3a"}]
})

route:l1
唯一吗?是的,l1是唯一的。实际上名称和路线不需要相同,但为了简单起见,我保留了它们。感谢您根据场景提供了一个恰当的答案。在这里和那里做了一些小调整:)使用地图迭代文档的方式对我来说是新的。很高兴奖励奖金。更新孩子们给出相同的500错误,可能无法解决我的主要问题。例如,在级别1更新中,“children.$[].children.$[].route.0”:“m1”创建500 mongo错误。Testcase是在“children.$[].children.$[].route.0”时出现的“不存在。但是@s7vr将路由划分为阵列的想法太棒了。毫无疑问,当我编辑项目的预先存在的值并消除首先读取值的需要时,它会带来更清晰的信息。尽管看起来整体更新执行可能需要类似的努力(考虑到500个错误)。我真诚地感谢您花时间和一个很棒的方法来重新设计。非常欢迎您-我更新了答案,用不同的设计进一步简化了您的更新。我们现在可以使用聚合框架来填充引用。使用它,看看它是否适合您的用例。