Как удалить дубликаты документов в MongoDB 4.0

#mongodb

#mongodb

Вопрос:

Допустим, у меня есть следующие документы в коллекции sample :

 {_id: 1, comp_index1: "one", comp_index2: "AAA", field: "lots of text" }
{_id: 2, comp_index1: "two", comp_index2: "BBB", field: "mucho texto" }
{_id: 3, comp_index1: "one", comp_index2: "CCC", field: "more text" }
{_id: 4, comp_index1: "two", comp_index2: "AAA", field: "más texto" }
{_id: 5, comp_index1: "one", comp_index2: "AAA", field: "lots of text" }
  

Я хочу создать comp_index1 и comp_index2 фактический уникальный составной индекс.

Если я запущу db.sample.createIndex( { comp_index1: 1, comp_index2: 1}, { unique: true } ) , это приведет E11000 duplicate key error collection к сбою, поэтому я решил сначала удалить дубликаты (из-за удаления опции выпадающих списков).

Прямо сейчас у меня есть этот алгоритм грубой силы, который выполняет эту работу:

 db.sample.aggregate([
    {
        $group: {
            _id: {
                comp_index1: "$comp_index1",
                comp_index2: "$comp_index2"
            },
            count: { $sum: 1 }
        }
    },
    {
        $match: { count: { $gt: 1 } }
    }
], { allowDiskUse: true }).forEach(function (doc) {
    for (i = 1; i < doc.count; i  ) {
        db.sample.remove({
            comp_index1: doc._id.comp_index1,
            comp_index2: doc._id.comp_index2
        },
        {
            justOne: true
        });
    }
    print("Removed "   (i-1)   " dups of <"   doc._id.comp_index1   " "   doc._id.comp_index2   ">")
})
  

Проблема в том, что у меня более 1,4 млн документов и почти 200 000 дубликатов, так что на это уходит целая вечность, поэтому мне было интересно, есть ли более быстрый и эффективный подход.

Ответ №1:

Через несколько часов мне наконец удалось найти решение в 1000 раз быстрее.

 var ids = [];
db.sample.aggregate([
    {
        $group: {
            _id: {
                comp_index1: "$comp_index1",
                comp_index2: "$comp_index2"
            },
            unique_ids: { $addToSet: "$_id" }
            count: { $sum: 1 }
        }
    },
    {
        $match: { count: { $gt: 1 } }
    }
], { allowDiskUse: true }).forEach(function (doc) {
    var i = 0;
    doc.unique_ids.forEach(function (id) {
        if (i   > 0) ids.push(id);
    })
})
db.sample.remove({"_id": {$in: ids}});
  

Несмотря на то, что в целом используется один и тот же подход, сохранение в ОЗУ всех идентификаторов для удаления и последующее выполнение remove с помощью оператора $in намного быстрее. Для выполнения этого потребовалось всего несколько секунд.

Если вы придумаете другое решение, которое не требует использования оперативной памяти, пожалуйста, поделитесь.

Ответ №2:

Недавно я создал код для удаления дублированных документов из MongoDB, это должно сработать:

 const query = [
  {
    $group: {
      _id: {
        comp_index1: "$comp_index1",
        comp_index2: "$comp_index2"
      },
      dups: {
        $addToSet: "$_id",
      },
      count: {
        $sum: 1,
      },
    },
  },
  {
    $match: {
      count: {
      $gt: 1,
      },
    },
  },
];

const cursor = collection.aggregate(query).cursor({ batchSize: 10 }).exec();

cursor.eachAsync((doc, i) => {
  doc.dups.shift(); // First element skipped for deleting
  doc.dups.map(async (dupId) => {
    await collection.findByIdAndDelete({ _id: dupId });
  });
});