2
votes

MongoDB - Mettre à jour toutes les entrées du tableau imbriqué uniquement si elles existent

J'ai un document imbriqué à plusieurs niveaux (sa dynamique et certains niveaux peuvent manquer mais maximum 3 niveaux). Je souhaite mettre à jour tous les itinéraires enfants et sous-enfants, le cas échéant. Le scénario est le même que dans n'importe quel explorateur Windows, où l'itinéraire de tous les sous-dossiers doit changer lorsqu'un itinéraire de dossier parent est modifié. Pour par exemple. Dans l'exemple ci-dessous, si je suis à route=="l1/l2a" et que son nom doit être modifié en "l2c", alors je mettrai à jour sa route comme route="l1/l2c et je route="l1/l2c jour la route de tous les enfants dire "l1/l2c/l3a" .

code 500 MongoError: The path 'children.1.children' must exist in the document in order to apply array updates.

Actuellement, je suis capable d'aller à un point et je peux changer son nom et UNIQUEMENT son itinéraire de la manière suivante:

updateChild = updatePath+'.children.$[].route'
updateChild2 = updatePath+'.children.$[].children.$[].route'
//update = { $set: { [updateChild]:'abc',[updateChild2]:'abc' } };

Le problème est que je ne peux pas modifier les routes de ses enfants, le cas échéant, c'est-à-dire la route du sous-dossier en tant que l1/l2c/l3a . Bien que j'aie essayé d'utiliser l'opérateur $[] comme suit.

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)
 })

Il est important que les niveaux soient personnalisables et donc je ne sais pas s'il y a "l3A" ou non. Comme il peut y avoir "l3A" mais il peut ne pas y avoir "l3B". Mais mon code nécessite simplement chaque chemin correct sinon il donne une erreur

     {
    "name":"l1",
    "route": "l1",
    "children":
        [
            {
            "name": "l2a",
            "route": "l1/l2a",
            "children": 
                [
                    {
                    "name": "l3a",
                    "route": "l1/l2a/l3a"
                 }]
            },
            {
            "name": "l2b",
            "route": "l1/l2b",
            "children": 
                [
                    {
                    "name": "l3b",
                    "route": "l1/l2b/l3b"
                 }]
            }
      ]
     }

La question est donc de savoir comment appliquer des modifications à l'aide de $ set à un chemin qui existe réellement et comment puis-je modifier la partie d'itinéraire existante. Si le chemin existe, c'est bien et bien et si le chemin n'existe pas, j'obtiens l'ERREUR.


2 commentaires

est route:l1 unique?


Oui, l1 est unique. En fait, les noms et les itinéraires n'ont pas besoin d'être les mêmes, mais pour simplifier, je les ai gardés identiques.


3 Réponses :


1
votes

vous ne pouvez pas faire ce que vous voulez. Parce que mongo ne le supporte pas. Je peux vous proposer de récupérer l'article dont vous avez besoin chez mongo. Mettez-le à jour avec l'aide de votre fonction récursive personnalisée. Et faites db.collection.updateOne(_id, { $set: data })

function updateRouteRecursive(item) {
  // case when need to stop our recursive function
  if (!item.children) {
    // do update item route and return modified item
    return item;
  }

  // case what happen when we have children on each children array
}


0 commentaires

1
votes

Je ne pense pas que ce soit possible avec arrayFilted pour la arrayFilted à arrayFilted de premier niveau et de deuxième niveau, mais oui, ce n'est possible que pour la mise à jour de troisième niveau,

La manière possible est que vous puissiez utiliser la mise à jour avec un pipeline d'agrégation à partir de MongoDB 4.2 ,

Je suggère juste une méthode, vous pouvez simplifier davantage à ce sujet et réduire la requête selon votre compréhension!

Utilisez $map pour parcourir la boucle du tableau des enfants et vérifier la condition en utilisant $cond , et fusionner des objets en utilisant $mergeObjects ,

// LEVEL 3 Example Values in variables
// let oldname = "l3a";
// let route = "l1/l2a/l3a";
// let newname = "l3g";
else if (segments.length === 3) {
    let result = await NavItems.updateOne(
        { _id: id },
        [{
            $set: {
                children: {
                    $map: {
                        input: "$children",
                        as: "a2",
                        in: {
                            $mergeObjects: [
                                "$$a2",
                                {
                                    $cond: [
                                        { $eq: ["$$a2.name", segments[1]] },
                                        {
                                            children: {
                                                $map: {
                                                    input: "$$a2.children",
                                                    as: "a3",
                                                    in: {
                                                        $mergeObjects: [
                                                            "$$a3",
                                                            {
                                                                $cond: [
                                                                    { $eq: ["$$a3.name", oldname] },
                                                                    {
                                                                        name: newname,
                                                                        route: { $concat: ["$name", "/", "$$a2.name", "/", newname] }
                                                                    },
                                                                    {}
                                                                ]
                                                            }
                                                        ]
                                                    }
                                                }
                                            }
                                        },
                                        {}
                                    ]
                                }
                            ]
                        }
                    }
                }
            }
        }]
    );
}

MISE À JOUR NIVEAU 1: Playground

// LEVEL 2: Example Values in variables
// let oldname = "l2a";
// let route = "l1/l2a";
// let newname = "l2g";
else if (segments.length === 2) {
    let result = await NavItems.updateOne(
        { _id: id },
        [{
            $set: {
                children: {
                    $map: {
                        input: "$children",
                        as: "a2",
                        in: {
                            $mergeObjects: [
                                "$$a2",
                                {
                                    $cond: [
                                        { $eq: ["$$a2.name", oldname] },
                                        {
                                            name: newname,
                                            route: { $concat: ["$name", "/", newname] },
                                            children: {
                                                $map: {
                                                    input: "$$a2.children",
                                                    as: "a3",
                                                    in: {
                                                        $mergeObjects: [
                                                            "$$a3",
                                                            { route: { $concat: ["$name", "/", newname, "/", "$$a3.name"] } }
                                                        ]
                                                    }
                                                }
                                            }
                                        },
                                        {}
                                    ]
                                }
                            ]
                        }
                    }
                }
            }
        }]
    );
}

MISE À JOUR DE NIVEAU 2: Playground

// LEVEL 1: Example Values in variables
// let oldname = "l1";
// let route = "l1";
// let newname = "l4";
if(segments.length === 1) {
  let result = await NavItems.updateOne(
        { _id: id },
        [{
            $set: {
                name: newname,
                route: newname,
                children: {
                    $map: {
                        input: "$children",
                        as: "a2",
                        in: {
                            $mergeObjects: [
                                "$$a2",
                                {
                                    route: { $concat: [newname, "/", "$$a2.name"] },
                                    children: {
                                        $map: {
                                            input: "$$a2.children",
                                            as: "a3",
                                            in: {
                                                $mergeObjects: [
                                                    "$$a3",
                                                    { route: { $concat: [newname, "/", "$$a2.name", "/", "$$a3.name"] } }
                                                ]
                                            }
                                        }
                                    }
                                }
                            ]
                        }
                    }
                }
            }
        }]
    );
}

MISE À JOUR NIVEAU 3: Playground

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

let segments = route.split('/');

Pourquoi une requête séparée pour chaque niveau?

Vous pouvez faire une seule requête, mais cela mettra à jour les données de tous les niveaux chaque fois que vous aurez juste besoin de mettre à jour des données à un niveau ou des données de niveau particulier, je sais que c'est du code et des requêtes longs, mais je peux dire que c'est une version optimisée pour l'opération de requête.


1 commentaires

Merci de fournir une réponse appropriée selon le scénario. A pris de petits ajustements ici et là :) La façon d'itérer le document en utilisant les cartes était quelque chose de nouveau pour moi. Heureux d'attribuer la prime.



3
votes

Mise à jour

Vous pouvez simplifier les mises à jour lorsque vous utilisez des références.Les mises à jour / insertions sont simples car vous ne pouvez mettre à jour que le niveau cible ou insérer un nouveau niveau sans vous soucier de la mise à jour de tous les niveaux. Laissez l'agrégation se charger de remplir tous les niveaux et de générer le champ d'itinéraire.

Exemple de travail - https://mongoplayground.net/p/TKMsvpkbBMn

Structure

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

]

Insérer une requête

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"}]
})

Mettre à jour la requête

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

Agrégation

[
  {
    "_id": 1,
    "name": "l1",
    "route": "l1",
    "children": [
      {
        "name": "l2a",
        "route": [
          "l1",
          "l2a"
        ],
        "children": [
          {
            "name": "l3a",
            "route": [
              "l1",
              "l2a",
              "l3a"
            ]
          }
        ]
      }
    ]
  }
]

Original

Vous pouvez créer un itinéraire en tant que type et format de tableau avant de le présenter à l'utilisateur. Cela simplifiera grandement les mises à jour pour vous. Vous devez diviser les requêtes en plusieurs mises à jour lorsque les niveaux imbriqués n'existent pas (ex mise à jour de niveau 2). Peut être utiliser des transactions pour effectuer plusieurs mises à jour de manière atomique.

Quelque chose comme

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"
  }}
])

mise à jour niveau 1

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

mise à jour niveau 2

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

mise à jour niveau 3

[
  {
    "_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": []
  }


2 commentaires

Les mises à jour pour les enfants donnent la même erreur 500 et peuvent ne pas résoudre mon problème majeur. Par exemple. Dans la mise à jour de niveau1, "children. $ []. Children. $ []. Route.0": "m1" crée l'erreur 500 mongo. Le cas de test est lorsque "children. $ []. Children. $ []. Route.0" n'existe pas. MAIS @ s7vr l'idée de diviser les routes en tableau est géniale. Cela apporte sans aucun doute plus de clarté lorsque je modifie les valeurs préexistantes des éléments et supprime le besoin de lire d'abord les valeurs. Bien qu'il semble que l'exécution globale de la mise à jour puisse nécessiter des efforts similaires (en ce qui concerne l'erreur 500). Je vous remercie sincèrement pour votre temps et votre approche géniale de la refonte.


Vous êtes les bienvenus - J'ai mis à jour la réponse pour simplifier encore plus vos mises à jour avec un design différent. Nous pouvons maintenant utiliser le cadre d'agrégation pour remplir les références. Jouez avec et voyez si cela correspond à votre cas d'utilisation.