2 votes

Ajouter un nouveau champ à tous les documents d'une collection avec la valeur du champ du document dans MongoDB (Mongoose) avec des enregistrements de 300K+.

J'ai des difficultés à ajouter un autre champ à tous les enregistrements de la base de données user avec une valeur de chaque document. J'ai l'habitude d'utiliser $set con db.model.updateMany y $addFields avec le pipeline d'agrégation, j'ai utilisé les deux pour résoudre des problèmes dans le passé, dans ce cas, je dois effectuer quelques logiques/calculs avant d'ajouter la valeur, et c'est là que se situe mon problème.

Disons que j'ai le schéma suivant :

{
  "users": [
    {
      "wallets": {...},
      "avatar": "",
      "isVerified": false,
      "suspended": false,
      "country": "Nigeria",
      "_id": "123",
      "resetPasswordToken": "",
      "email": "example@gmail.com",
      "phone": "08012398743",
      "name": "Agbakwuru Nnaemeka Kennedy ",
      "role": "user",
    },
    {...}
}

Je souhaite ajouter un nouveau champ phoneNumber qui prendra la valeur du champ existant phone mais avant de l'ajouter, j'aimerais effectuer une analyse logique, car certaines des valeurs du téléphone ont des espaces, la plupart d'entre elles ne sont pas correctement formatées, et j'aimerais ajouter le code du pays à l'adresse phone avant de l'ajouter à la nouvelle valeur phoneNumber champ.

J'ai pu le faire en utilisant un curseur de Mongoose db.mode.aggregate méthode, avec $match et en ajoutant le champ à chaque document avec l'agrégat $addFields pipeline, cela s'est avéré prendre beaucoup de temps, j'ai dû arrêter l'opération car elle prenait trop de temps à fonctionner.

J'aimerais croire qu'il existe une meilleure solution. Je vous remercie de votre aide.

Edita:

Voici l'agrégation que j'utilise :

const userCursor = User.aggregate([{$match: {phone: {$exists: true}}}]);
for await (const doc of userCursor) {
  await User.findByIdAndUpdate(doc._id, {$set: {
          phoneNumber: convertPhoneNumber({phoneNumber: doc.phone.replace(/\s+/g, "")})}
  });
}

En convertPhoneNumber est une méthode d'aide que j'ai définie dans mes utilitaires pour ajouter le code du pays au numéro de téléphone.

1voto

Elgringorrible Points 64

J'essaierais d'exécuter un script comme celui-là directement dans le fichier mongo ou Robo3T :

db.getCollection("users").find({}).forEach( doc => {

    doc.users.forEach( user => {

        // do your logic here
        let phoneNumber = "12345";
        phoneNumber = "+007" + phoneNumber;

        user.phoneNumber = phoneNumber;
    })

    db.users.save(doc);
})

Cela prendra encore un certain temps sur des documents de plus de 300 000, mais donnez-vous quelques minutes.

1voto

Takis _ Points 418

Vous pouvez utiliser $function et appeler ce code javascript dans la base de données.

Cela nécessite >=MongoDB 4.4

db.Users.update(
  {phone: {$exists: true}},
  [{$set: {phoneNumber:
            {
             "$function": {
             "body": YOUR_convertPhoneNumber_FUNCTION_DEF,
             "args": ["$phoneNumber"],
             "lang": "js"
             }
            }])

De même, si le code de convertPhoneNumber peut être écrit dans MongodBD en utilisant des opérateurs agrégés, vous pouvez également éviter le javascript.

Il s'agit d'une mise à jour de pipeline et nous pouvons utiliser tous les opérateurs d'agrégats lors de la mise à jour.


Editar

Si la mangouste a des problèmes avec le $function ou la méthode du pilote nodejs a un problème avec la mise à jour du pipeline, vous pouvez également le faire.

db.runCommand(
   {
      update: "yourCollectionName",
      updates: [
         {
           q: {phone: {$exists: true}},
           u: 
           [{$set: {phoneNumber:
            {
             "$function": {
             "body": YOUR_convertPhoneNumber_FUNCTION_DEF,
             "args": ["$phoneNumber"],
             "lang": "js"
             }
            }],
           multi: true
         }
      ],
      ordered: false
   }
)

0voto

Wernfried Points 2508

Vous pouvez essayer Fonctionnement en vrac Cette opération permet de mettre à jour la collection par lots de 1000 documents :

var bulkOperations = [];
db.getCollection("users").find({}).forEach(doc => {
   doc.users.forEach(user => {
      user.phoneNumber = convertPhoneNumber({phoneNumber: user.phone.replace(/\s+/g, "")});
   })
   bulkOperations.push({
      updateOne: {
         filter: { id: doc._id },
         update: { $set: { users: doc.users } }
      }
   });
   if (bulkOperations.length > 1000) {
      db.getCollection("users").bulkWrite(bulkOperations, { ordered: false });
      bulkOperations = [];
   }
})
if (bulkOperations.length > 0) 
   db.getCollection("users").bulkWrite(bulkOperations, { ordered: false });

0voto

Eazy Points 73

Avec l'aide de @Jeremy Thille Réponse de la Commission aquí J'ai pu résoudre ce problème avec MongoDB Compass mongo en ligne de commande avec l'extrait ci-dessous.

db.users.find({phone: {$exists: true}}).forEach( user => {
  const phone = user.phone.replace(/\s+/g, "");
  const phoneNumber = `+234${phone.slice((phone.length - 10))}`;
  db.users.updateOne({_id: user._id}, {$set: {phoneNumber}});
})

L'inconvénient est qu'il fallait environ 10 à 15 minutes pour mettre à jour 300 000 documents, ce qui représentait une amélioration considérable par rapport à ma mise en œuvre initiale qui prenait plus d'une journée pour mettre à jour quelques dizaines de milliers de documents.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X