MongoDB에서 중복 데이터를 추출/삭제하는 방법


이번에는 “MongoDB에서 중복 데이터를 추출/삭제하는 방법"에 대해 알아보겠다.

createIndex()으로 고유한 제약 조건을 걸려고 할 때, 중복 데이터가 있으면 걸 수 없다. 이번에는 그럴 때 필요한 “중복 데이터를 추출하여 삭제하는 구체적인 방법"에 대해 알아보겠다.

사전 데이터 등록

아래의 데이터를 mongo 명령으로 일괄 등록한 상태에서 중복된 데이터({ item: "pen" })의 삭제에 대해 살펴보겠다.

> db.orders.insertMany([
  { datetime: ISODate("2021-12-15T12:00:00+09:00"), item: "pen", amount: 70 },
  { datetime: ISODate("2021-12-15T12:00:00+09:00"), item: "note", amount: 80 },
  { datetime: ISODate("2021-12-15T12:00:00+09:00"), item: "eraser", amount: 100 },
  { datetime: ISODate("2021-11-13T12:00:00+09:00"), item: "pen", amount: 20 },
  { datetime: ISODate("2021-11-02T12:00:00+09:00"), item: "pen", amount: 20 },
  { datetime: ISODate("2021-10-23T12:00:00+09:00"), item: "pen", amount: 30 },
  { datetime: ISODate("2021-10-18T12:00:00+09:00"), item: "pen", amount: 10 }
]);

중복 데이터 추출

중복 데이터만을 추출하려면 aggregate()$group을 사용하여 추출할 수 있다. 추출할 때 $$ROOT를 사용하여 원본 데이터를 그대로 items 배열로 푸시할 수 있다.

또한, aggregate()의 제약으로 가능한 메모리 조작은 100MB까지로 되어 있으므로, 이를 넘는 데이터를 한번에 조작하려는 경우에는 옵션으로 allowDiskUse을 지정한다.

코드 (index.js)

var MongoClient = require("mongodb").MongoClient;
var URL = "mongodb://localhost:27017";
 
MongoClient.connect(URL, (err, client) => {
  if (err) {
    console.log(err);
    return;
  };
 
  var db = client.db("sample");
 
  db.collection("orders").aggregate([
    // 중복 데이터 추출
    {
      $group: {
        _id: "$item",
        items: { $push: "$$ROOT" },
        count: { $sum: 1 }
      }
    },
    {
      $match: { count: { $gt: 1 } }
    }
  ]).toArray().then((docs) => {
    console.log(docs);
  }).catch((err) => {
    console.log(err);
  }).then(() => {
    client.close();
  });
});

실행 & 결과

> node .\index.js
[ { _id: 'pen',
    items: [ [Object], [Object], [Object], [Object], [Object] ],
    count: 5 } ]

실행 결과는 [Object]라고 표시되어 있지만, 디버그로 중지시켜 확인할 때에 JSON.stringify()를 사용하면 내용을 확인할 수 있다.

중복 데이터 삭제

중복 데이터를 삭제라고는 했지만, 1건의 데이터는 남기기 위해 어떤 데이터를 남기고 그 이외를 삭제할 수 있도록 하는지에 대해 여기에서는 2가지 방법을 소개한다.

  • 상단 데이터를 결정하여 남긴다
  • ObjectId 지정으로 남긴다

상단 데이터를 결정하여 남긴다

우선 정렬하여 “어떤 값이 가장 큰(or 가장 작은) 데이터를 남기는” 방법이다.

샘플 코드의 L.14로 정렬하여, L.30으로 최초의 1건을 제거하고 나서 삭제하고 있다.

코드 (index.js)

var MongoClient = require("mongodb").MongoClient;
var URL = "mongodb://localhost:27017";
 
MongoClient.connect(URL, (err, client) => {
  if (err) {
    console.log(err);
    return;
  };
 
  var db = client.db("sample");
 
  db.collection("orders").aggregate([
    {
      $sort: { amount: -1 }
    },
    {
      $group: {
        _id: "$item",
        targets: { $push: "$_id" },
        count: { $sum: 1 }
      }
    },
    {
      $match: { count: { $gt: 1 } }
    },
  ]).toArray().then((docs) => {
    console.log(JSON.stringify(docs));
    var procs = [];
    for (var doc of docs) {
      doc.targets.shift();
      procs[procs.length] = db.collection("orders").deleteMany({
        _id: { $in: doc.targets }
      });
    }
    return Promise.all(procs);
  }).then((results) => {
    console.log("Remove dupulicate data.");
  }).catch((err) => {
    console.log(err);
  }).then(() => {
    client.close();
  });
});

실행 & 결과

> node .\index.js
Remove dupulicate data.

ObjectId 지정으로 남긴다

어느 쪽인가 하면 데이터 확인한 후에 남기고 싶은 데이터를 지정하여 그 이외를 삭제하는 작업이 일반적이라고 생각된다. 이 방법은 남겨두고 싶은 데이터의 ObjectId 를 지정하여 그 이외를 삭제하도록(듯이) 하는 방법이다. 제외 데이터의 지정은 이하의 샘플 코드 L.32 에 있는 배열에 콤마 단락으로 추가하는 것으로 지정할 수 있다.

코드 (index.js)

var MongoClient = require("mongodb").MongoClient;
var ObjectId = require("mongodb").ObjectId;
var URL = "mongodb://localhost:27017";
 
MongoClient.connect(URL, (err, client) => {
  if (err) {
    console.log(err);
    return;
  };
 
  var db = client.db("sample");
 
  db.collection("orders").aggregate([
    // 중복 데이터 추출한다.
    {
      $group: {
        _id: "$item",
        items: { $push: "$$ROOT" },
        count: { $sum: 1 }
      }
    },
    {
      $match: { count: { $gt: 1 } }
    },
    // 중복 데이터만을 가져온다.
    {
      $unwind: "$items"
    },
    // 남은 데이터를 삭제 대상부터 제거
    {
      $match: {
        "items._id": { $nin: [new ObjectId("5a5091c4ff3735d50c439d41")] }
      }
    },
    // 삭제 대상의 ObjectId를 모은다.
    {
      $group: {
        _id: null,
        targets: { $push: "$items._id" }
      }
    }
  ]).toArray().then((docs) => {
    console.log(JSON.stringify(docs));
    var procs = [];
    for (var doc of docs) {
      procs[procs.length] = db.collection("orders").deleteMany({
        _id: { $in: doc.targets }
      });
    }
    return Promise.all(procs);
  }).then((results) => {
    console.log("Remove dupulicate data.");
  }).catch((err) => {
    console.log(err);
  }).then(() => {
    client.close();
  });
});

실행 & 결과

> node .\index.js
Remove dupulicate data.

마무리

“MongoDB에서 중복 데이터를 추출/삭제하는 방법"에 대해 알아보았다. 포인트는 다음과 같습니다.

  • 데이터 추출은 aggregate()$group
  • 데이터 삭제는 aggregate()를 하여 deleteMany()