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 配列へ push できる。
また、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 指定で残す
先頭データを決めて残す
まず、ソートして「ある値が最大、または最小のデータを残す」方法である。
サンプルコードでは 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()