Как удалить дубликаты в mongodb на основе нескольких полей? - PullRequest
1 голос
/ 29 мая 2020

Вот пример моих документов:

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

Я хочу удалить, если есть несколько документов, содержащих одно и то же имя и одинаковое значение. В приведенном выше примере он удалит один документ, {name:"duplicate", value:true, id:2910921} или {name:"duplicate", value:true, id:32838293}, мне не важно, какой именно.

До сих пор я рассматривал возможность создания нового поля для каждого из них, которое было бы чем-то вроде newField: "duplicatetrue", а затем я мог бы просто использовать отдельные поля для удаления дубликатов, но у меня возникли проблемы выяснение того, как объединить два разных поля с разными типами в новое поле. Я определенно открыт и для лучших предложений. Вот что у меня есть на данный момент:

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

Однако приведенная выше строка не выводит значения, а выводит именно newField: ["$ name", "$ value"]

Удаление кавычек из $ name и $ value тоже не работает.

Я использую драйвер Node mongodb: 3.5.8

Ответы [ 3 ]

1 голос
/ 29 мая 2020

Вы можете сделать это двумя способами

  1. В одном вызове БД: Используя оператор агрегации $ out , Возможно, вы также можете использовать $ merge , но это не очень полезно для вашего случая.
  2. В двух вызовах БД: Как будто вы думаете, что $out деструктивен и с миллионами документов в коллекции может быть проблема в производственной среде, то вы можете сначала прочитать все _id документы, которые нужно удалить, и использовать .deleteMany () , чтобы удалить все документы сразу. (Вы можете использовать любой уникальный идентификатор в do c вместо _id, но я использовал _id, поскольку он проиндексирован по умолчанию, что может помочь запустить deleteMany() быстрее).

Шаг 1:

Использование $out - Как я уже сказал, это деструктивно, потому что он переопределит всю коллекцию, если имя ввода совпадает, или создаст новую коллекцию с помощью результат вашего запроса агрегирования. Поэтому хорошо проверьте свой запрос на агрегирование, прежде чем использовать $out в качестве последнего этапа. Также записывайте данные во временную коллекцию и переименовывайте коллекции, когда все в порядке. Учитывайте время простоя при переименовании коллекций

Запрос:

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

Тест: mongoplayground

Шаг 2:

  1. Используя запрос агрегирования, вы получите список _ids, которые нужно удалить из коллекции.

Запрос:

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 [] */
  ])

Тест: mongoplayground

Теперь используется .deleteMany() - удалите все документы:

Запрос:

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

Рассмотрение до .deleteMany() необходимо проверить агрегирование Результат не является пустым массивом [] и имеет поле do c с _idsNeedsToBeDeleted, которое является массивом. Кроме того, поскольку мы сопоставляем _id в БД - массив агрегатов _idsNeedsToBeDeleted будет массивом строк - Итак, перебираем массив, конвертируем строку в ObjectId() и используем этот массив ObjectId() в запросе на удаление.

Примечание:

Независимо от того, какой шаг вы выберете - поскольку мы группируемся по name + value, вам необходимо убедиться, что все ваши документы имеют эти поля.

1 голос
/ 29 мая 2020

Я не уверен насчет mon go, но с помощью узла вы можете удалить дубликаты. Я попробовал этот метод для одного из требований, который работал нормально. пожалуйста, попробуйте это, изменив, так как вам нужны имена переменных.

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);
0 голосов
/ 29 мая 2020

Выполнение этого изначально в node, похоже, помогло (вероятно, не самый быстрый и эффективный способ, но сработало следующее):

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