1
votes

Comment supprimer les doublons dans mongodb en fonction de plusieurs champs?

Voici un exemple de mes documents:

db.collection(collectionName).updateMany({}, {$set: {"newField": ["$name","$value"]  }})

Je souhaite supprimer s'il existe plusieurs documents qui contiennent le même nom et la même valeur. Dans l'exemple ci-dessus, il supprimerait un document, soit {name: "duplicate", value: true, id: 2910921} ou {name: "duplicate", value: true, id: 32838293} , peu m'importe lequel.

Jusqu'à présent, j'ai simplement envisagé de créer un nouveau champ pour chacun d'entre eux qui serait quelque chose comme newField: "duplicatetrue" et ensuite je pourrais simplement utiliser distinct sur ceux-ci pour supprimer les dupes, mais j'ai difficulté à comprendre comment concaténer deux champs différents avec des types différents dans un nouveau champ. Je suis également ouvert à de meilleures suggestions. Voici ce que j'ai jusqu'à présent:

[{name:"duplicate", value:true, id:2910921},{name:"duplicate", value:true, id:32838293},{name:"duplicate", value:false, id:3283232},{name:"notDuplicate", value:true, id:382932}]

Cependant, la ligne ci-dessus ne renvoie pas les valeurs, elle affiche plutôt exactement newField: ["$ name", "$ value" ]

Supprimer les guillemets de $ name et $ value ne fonctionne pas non plus.

J'utilise le pilote Node mongodb: 3.5.8


4 commentaires

quelle est votre version MongoDB?


@whoami mongodb driver: 3.5.8: npmjs.com/package/mongodb , c'est ça tu veux dire?


non, c'est le pilote node.js que vous utilisez pour vous connecter au serveur MongoDB, je demande la version du serveur MongoDB.


mongod - sorties de version: v4.2.3


3 Réponses :


1
votes

Je ne suis pas sûr de mongo mais en utilisant le nœud, vous pouvez supprimer les doublons. J'ai essayé cette méthode pour l'une des exigences qui fonctionnait bien. Veuillez essayer ceci en changeant car vous avez besoin de noms de var.

function arrUnique(arr) {
    var cleaned = [];
    arr.forEach(function(itm) {
        var unique = true;
        cleaned.forEach(function(itm2) {
            if (_.isEqual(itm, itm2)) unique = false;
        });
        if (unique)  cleaned.push(itm);
    });
    return cleaned;
}

var newField = arrUnique(newField);


2 commentaires

Merci beaucoup, vous m'avez mis à 100% dans la bonne direction en faisant cela dans node et avec l'utilisation d'un foreach, malheureusement, votre fonction n'a pas fonctionné parfaitement pour moi car il semble que cela nécessite du lodash (?) Et je Je crois qu'il a renvoyé un tableau de valeurs uniques plutôt que toutes les différentes valeurs que je pourrais supprimer de la collection.


yah son mon plaisir ici son a donné une idée u. si vous avez de l'aide, donnez aussi un vote positif ;-)



0
votes

Le faire nativement dans node semblait faire l'affaire (probablement pas le moyen le plus rapide ou le plus efficace mais le suivant a fonctionné):

const array = await db.collection(collectionName).find({}).toArray();
const newArr = array.map((item) => {
  const newObj = Object.assign({}, item, {
    hiWorld: `${item.name}${item.amount}`,
  });
  return newObj;
});
var uniqueItems = [];
var duplicateIds = [];
newArr.forEach((item) => {
  if (uniqueItems.includes(item.hiWorld)) {
    duplicateIds.push(item._id);
  } else {
    uniqueItems.push(item.hiWorld);
  }
});

await db.collection(collectionName).deleteMany({ _id: { $in: duplicateIds } });


1 commentaires

Vous pouvez essayer l'agrégation pour obtenir duplicateIds au lieu de lire tous les documents à coder :-) Même si vous devez simplement utiliser projection pour lire uniquement certains champs nécessaires.



1
votes

Vous pouvez le faire de deux manières

  1. Dans un appel DB: Utilisation de l'opérateur d'agrégation $ out , vous pouvez peut-être aussi utiliser $ merge mais ce n'est pas très utile pour votre cas.
  2. En deux appels à la base de données: Comme si vous pensez que $ out est destructif et que des millions de documents en collection peuvent être un problème dans l'environnement de production, vous pouvez d'abord lire tous les _id des documents à supprimer et utiliser . deleteMany () pour supprimer tous les documents à la fois. (Vous pouvez utiliser n'importe quel identifiant unique sur un document au lieu de _id mais j'ai utilisé _id car il est indexé par défaut - ce qui peut aider à exécuter deleteMany () plus rapide).

Étape 1:

Utilisation de $ out - Donc, comme je l'ai dit, c'est destructif car il remplacera l'ensemble collection si le nom d'entrée correspond ou créera une nouvelle collection par le résultat de votre requête d'agrégation. Donc, testez très bien votre requête d'agrégation avant d'utiliser $ out comme dernière étape. Écrivez également des données dans la collection temporaire et renommez les collections une fois que tout est assez bon. Considérez un temps d'arrêt lors du changement de nom des collections

Requête:

db.collection.deleteMany( { "_id" : {$in : [_ids]} } );

Test: mongoplayground

Étape 2:

  1. À l'aide d'une requête d'agrégation, vous obtiendrez la liste des _ids à supprimer de la collection.

Requête:

db.collection.aggregate([
    /**
     * Group on matching docs :
     * { name: "duplicate", value: false}, 
     * { name: "duplicate", value: true}, 
     * { name: "duplicate-yes", value: true},
     * { name: "notDuplicate", value: true} 
     * */
    {
      $group: {
        _id: { name: "$name", value: "$value" },
        _idsNeedsToBeDeleted: { $push: "$$ROOT._id" } // push all `_id`'s to an array
      }
    },
    /** Remove first element - which is removing a doc */
    {
      $project: {
        _id: 0,
        _idsNeedsToBeDeleted: { $slice: [ "$_idsNeedsToBeDeleted", 1, { $size: "$_idsNeedsToBeDeleted" } ] }
      }
    },
    {
      $unwind: "$_idsNeedsToBeDeleted" // Unwind `_idsNeedsToBeDeleted`
    },
    /** Group without a condition & push all `_idsNeedsToBeDeleted` fields to an array */
    {
      $group: { _id: "", _idsNeedsToBeDeleted: { $push: "$_idsNeedsToBeDeleted" } }
    },
    {$project : { _id : 0 }} // Optional stage
     /** At the end you'll have an [{ _idsNeedsToBeDeleted: [_ids] }] or [] */
  ])

Test: mongoplayground

  1. Maintenant en utilisant .deleteMany () - supprimez tous les documents:

Requête:

db.collection.aggregate([
  {
    $group: { _id: { name: "$name", value: "$value" },
      doc: { $last: "$$ROOT" } // Retrieve only last doc in a group
    }
  },
  {
    $replaceRoot: { newRoot: "$doc" } // replace doc as object as new root of document
  },
  { $out : 'collection_new' } // Test above aggregation & then use this 
])

Considération avant .deleteMany () , vous devez vérifier l'agrégation Le résultat n'est pas un tableau vide [] et a un document avec le champ _idsNeedsToBeDeleted qui est un tableau. De plus, comme nous comparons avec _id dans la base de données - le tableau des agrégations _idsNeedsToBeDeleted sera un tableau de chaînes - Donc, itérer sur le tableau, convertir la chaîne en ObjectId () & utiliser ce tableau de ObjectId () dans la requête de suppression.

Remarque:

Indépendamment de ce étape que vous choisissez - Puisque nous regroupons sur nom + valeur , vous devez vous assurer que tous vos documents contiennent ces champs.


2 commentaires

merci, j'apprécie les notes supplémentaires sur les raisons pour lesquelles je choisirais l'une des méthodes de l'autre et les commentaires expliquant chaque étape de l'agrégation, très utiles.


aussi, merci pour l'introduction à mongoplayground, c'est l'outil simple dont je ne savais pas avoir besoin pour tester les pipelines.