今回は「MongoDB で 重複データを抽出 / 削除する方法」についてまとめます。
createIndex()
で 一意制約(ユニークキー制約) をかけようとしたとき、重複データがあるとかけられません。
今回はそんなときに必要となる「重複データを抽出して削除する具体的な方法」についてまとめました。
前提データ
以下のデータを mongo コマンドで投入してある状態で重複しているデータ( { item: "pen" }
)の削除を見ていきます。
> db.orders.insertMany([ { datetime: ISODate("2017-12-15T12:00:00+09:00"), item: "pen", amount: 70 }, { datetime: ISODate("2017-12-15T12:00:00+09:00"), item: "note", amount: 80 }, { datetime: ISODate("2017-12-15T12:00:00+09:00"), item: "eraser", amount: 100 }, { datetime: ISODate("2017-11-13T12:00:00+09:00"), item: "pen", amount: 20 }, { datetime: ISODate("2017-11-02T12:00:00+09:00"), item: "pen", amount: 20 }, { datetime: ISODate("2017-10-23T12:00:00+09:00"), item: "pen", amount: 30 }, { datetime: ISODate("2017-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通りの方法を紹介します。
先頭データを決め打ちで残す
最初にソートしておくことで「ある値がもっとも大きな(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()
参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!
最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!