mongodb에서 중복 문서를 가장 빨리 제거하는 방법
mongodb(향후 10m+)에 약 170만 건의 문서를 보유하고 있습니다.그 중 일부는 제가 원하지 않는 중복 항목을 나타냅니다.문서의 구조는 다음과 같습니다.
{
_id: 14124412,
nodes: [
12345,
54321
],
name: "Some beauty"
}
이름이 같은 다른 문서와 동일한 노드가 하나 이상 있는 경우 문서는 중복됩니다.중복을 제거하는 가장 빠른 방법은 무엇입니까?
dropDups: true
3.0에서는 옵션을 할 수 .
중복 항목을 수집한 다음 한 번에 제거할 수 있는 집계 프레임워크가 있는 솔루션이 있습니다.
시스템 수준 "인덱스" 변경보다 다소 느릴 수 있습니다.그러나 중복된 문서를 제거하는 방법을 고려하는 것이 좋습니다.
한 번에 모든 문서 제거
var duplicates = [];
db.collectionName.aggregate([
{ $match: {
name: { "$ne": '' } // discard selection criteria
}},
{ $group: {
_id: { name: "$name"}, // can be grouped on multiple properties
dups: { "$addToSet": "$_id" },
count: { "$sum": 1 }
}},
{ $match: {
count: { "$gt": 1 } // Duplicates considered as count greater than one
}}
],
{allowDiskUse: true} // For faster processing if set is larger
) // You can display result until this and check duplicates
.forEach(function(doc) {
doc.dups.shift(); // First element skipped for deleting
doc.dups.forEach( function(dupId){
duplicates.push(dupId); // Getting all duplicate ids
}
)
})
// If you want to Check all "_id" which you are deleting else print statement not needed
printjson(duplicates);
// Remove all duplicates in one go
db.collectionName.remove({_id:{$in:duplicates}})
문서를 하나씩 삭제할 수 있습니다.
db.collectionName.aggregate([
// discard selection criteria, You can remove "$match" section if you want
{ $match: {
source_references.key: { "$ne": '' }
}},
{ $group: {
_id: { source_references.key: "$source_references.key"}, // can be grouped on multiple properties
dups: { "$addToSet": "$_id" },
count: { "$sum": 1 }
}},
{ $match: {
count: { "$gt": 1 } // Duplicates considered as count greater than one
}}
],
{allowDiskUse: true} // For faster processing if set is larger
) // You can display result until this and check duplicates
.forEach(function(doc) {
doc.dups.shift(); // First element skipped for deleting
db.collectionName.remove({_id : {$in: doc.dups }}); // Delete remaining duplicates
})
된 문서를 합니다.name
+nodes
할 수 .unique
다음 옵션을 사용한 색인:
db.test.ensureIndex({name: 1, nodes: 1}, {unique: true, dropDups: true})
문서에 나와 있듯이 데이터베이스에서 데이터가 삭제되므로 이 작업에 매우 주의해야 합니다.데이터베이스가 예상대로 작동하지 않을 경우 먼저 데이터베이스를 백업합니다.
갱신하다
은 "2.x"라는으로 MongoDB 2합니다.dropDups
3.0(하드웨어)에서는 옵션을 더 이상 사용할 수 없습니다.
mongodump를 사용하여 수집 덤프 생성
수집 지우기
고유 인덱스 추가
mongore restore를 사용하여 컬렉션 복원
MongoDB 3.4에서 작동하는 이 솔루션을 찾았습니다: 중복이 있는 필드를 fieldX라고 가정하겠습니다.
db.collection.aggregate([
{
// only match documents that have this field
// you can omit this stage if you don't have missing fieldX
$match: {"fieldX": {$nin:[null]}}
},
{
$group: { "_id": "$fieldX", "doc" : {"$first": "$$ROOT"}}
},
{
$replaceRoot: { "newRoot": "$doc"}
}
],
{allowDiskUse:true})
mongoDB를 처음 접하는 저는 많은 시간을 들였고 중복되는 것을 찾아 삭제하기 위해 다른 긴 솔루션을 사용했습니다.하지만, 저는 이 해결책이 깔끔하고 이해하기 쉽다고 생각합니다.
필드 X가 포함된 문서를 먼저 일치시키는 방식으로 작동합니다(이 필드가 없는 일부 문서를 가지고 있었는데 추가로 빈 결과가 하나 더 있습니다).
다음 단계에서는 필드 X별로 문서를 그룹화하고 $$ROOT를 사용하여 각 그룹에 $first 문서만 삽입합니다.마지막으로 $first 및 $$ROOT를 사용하여 찾은 문서로 전체 집계 그룹을 대체합니다.
컬렉션이 크기 때문에 allowDiskUse를 추가해야 했습니다.
파이프라인 개수에 상관없이 추가할 수 있으며, $first에 대한 문서에 $first를 사용하기 전에 정렬 단계가 언급되어 있지만, $first를 사용하기 전에 저에게 적합했습니다.여기에 링크를 게시할 수 없습니다. 제 평판은 10 미만입니다. :( "
$out 단계를 추가하여 결과를 새 컬렉션에 저장할 수 있습니다...
또는 replaceRoot 없이 그룹 단계에서 전체 문서가 아닌 field1, field2와 같은 일부 필드에만 관심이 있는 경우:
db.collection.aggregate([
{
// only match documents that have this field
$match: {"fieldX": {$nin:[null]}}
},
{
$group: { "_id": "$fieldX", "field1": {"$first": "$$ROOT.field1"}, "field2": { "$first": "$field2" }}
}
],
{allowDiskUse:true})
다음 Mongo 집계 파이프라인은 중복제거를 수행하여 동일하거나 다른 컬렉션으로 다시 출력합니다.
collection.aggregate([
{ $group: {
_id: '$field_to_dedup',
doc: { $first: '$$ROOT' }
} },
{ $replaceRoot: {
newRoot: '$doc'
} },
{ $out: 'collection' }
], { allowDiskUse: true })
제 DB에는 수백만 개의 중복 레코드가 있었습니다.@수백만 개의 중복된 기록을 삭제하려는 사람들에게 효과적인 해결책을 쓰는 것처럼, nath의 대답은 효과가 없었습니다.
/** Create a array to store all duplicate records ids*/
var duplicates = [];
/** Start Aggregation pipeline*/
db.collection.aggregate([
{
$match: { /** Add any filter here. Add index for filter keys*/
filterKey: {
$exists: false
}
}
},
{
$sort: { /** Sort it in such a way that you want to retain first element*/
createdAt: -1
}
},
{
$group: {
_id: {
key1: "$key1", key2:"$key2" /** These are the keys which define the duplicate. Here document with same value for key1 and key2 will be considered duplicate*/
},
dups: {
$push: {
_id: "$_id"
}
},
count: {
$sum: 1
}
}
},
{
$match: {
count: {
"$gt": 1
}
}
}
],
{
allowDiskUse: true
}).forEach(function(doc){
doc.dups.shift();
doc.dups.forEach(function(dupId){
duplicates.push(dupId._id);
})
})
/** Delete the duplicates*/
var i,j,temparray,chunk = 100000;
for (i=0,j=duplicates.length; i<j; i+=chunk) {
temparray = duplicates.slice(i,i+chunk);
db.collection.bulkWrite([{deleteMany:{"filter":{"_id":{"$in":temparray}}}}])
}
다음은 조금 더 '수동적'인 방법입니다.
기본적으로 먼저 관심 있는 모든 고유 키 목록을 가져옵니다.
그런 다음 각 키를 사용하여 검색을 수행하고 검색 결과가 1보다 크면 삭제합니다.
db.collection.distinct("key").forEach((num)=>{
var i = 0;
db.collection.find({key: num}).forEach((doc)=>{
if (i) db.collection.remove({key: num}, { justOne: true })
i++
})
});
문서의 일부만 복제되는 경우 속도를 높이는 팁:
- 중복 항목을 탐지하려면 필드에 색인이 있어야 합니다.
- $group은 인덱스를 사용하지 않지만 $185를 사용할 수 있으며 $185는 인덱스를 사용합니다. 그래서 당신은 처음에 $185 스텝을 넣어야 합니다.
- $out to new collection 대신 delete_many()를 사용하면 IO 시간과 디스크 공간을 많이 절약할 수 있습니다.
Pymongo를 사용하면 다음을 수행할 수 있습니다.
index_uuid = IndexModel(
[
('uuid', pymongo.ASCENDING)
],
)
col.create_indexes([index_uuid])
pipeline = [
{"$sort": {"uuid":1}},
{
"$group": {
"_id": "$uuid",
"dups": {"$addToSet": "$_id"},
"count": {"$sum": 1}
}
},
{
"$match": {"count": {"$gt": 1}}
},
]
it_cursor = col.aggregate(
pipeline, allowDiskUse=True
)
# skip 1st dup of each dups group
dups = list(itertools.chain.from_iterable(map(lambda x: x["dups"][1:], it_cursor)))
col.delete_many({"_id":{"$in": dups}})
성능
저는 데이터베이스에 3,000만 개의 문서와 1TB의 대용량 문서를 포함하여 테스트합니다.
- 색인/정렬을 사용하지 않으면 커서를 가져오는 데 한 시간 이상이 걸립니다(환자가 기다릴 필요조차 없습니다).
- $out을 사용하여 새 컬렉션으로 출력합니다.파일 시스템이 스냅샷을 지원하지 않는 경우 더 안전합니다.하지만 SSD를 사용하고 있음에도 불구하고 디스크 공간이 많이 필요하고 완료하는 데 40분 이상이 걸립니다.HDD RAID를 사용하면 속도가 훨씬 느려집니다.
- 인덱스/인스턴스 및 inplace delete_many를 사용하면 총 5분 정도 소요됩니다.
다음 방법은 동일한 이름의 문서를 병합하면서 중복 없이 고유한 노드만 유지합니다.
를 사용하여 찾았습니다.$out
연산자는 간단한 방법입니다.저는 배열을 푼 다음 집합에 추가하여 그룹화합니다. 그$out
operator를 사용하면 집계 결과가 지속될 수 있습니다.컬렉션 이름을 입력하면 컬렉션이 새 데이터로 바뀝니다.이름이 존재하지 않으면 새 컬렉션이 만들어집니다.
이게 도움이 되길 바랍니다.
allowDiskUse
파이프라인에 추가해야 할 수 있습니다.
db.collectionName.aggregate([
{
$unwind:{path:"$nodes"},
},
{
$group:{
_id:"$name",
nodes:{
$addToSet:"$nodes"
}
},
{
$project:{
_id:0,
name:"$_id.name",
nodes:1
}
},
{
$out:"collectionNameWithoutDuplicates"
}
])
파이몬고를 사용하면 효과가 있을 것입니다.
unique_field에서 컬렉션에 대해 고유해야 하는 필드를 추가합니다.
unique_field = {"field1":"$field1","field2":"$field2"}
cursor = DB.COL.aggregate([{"$group":{"_id":unique_field, "dups":{"$push":"$uuid"}, "count": {"$sum": 1}}},{"$match":{"count": {"$gt": 1}}},{"$group":"_id":None,"dups":{"$addToSet":{"$arrayElemAt":["$dups",1]}}}}],allowDiskUse=True)
중복 횟수에 따라 dups 배열을 분할합니다(여기서는 모두에 대해 하나의 추가 중복만 있음).
items = list(cursor)
removeIds = items[0]['dups']
hold.remove({"uuid":{"$in":removeIds}})
주요 질문에 답할지는 모르겠지만, 다른 사람들에게는 유용할 것입니다. 1.findOne() 메서드를 사용하여 중복 행을 쿼리하고 개체로 저장합니다.
const User = db.User.findOne({_id:"duplicateid"});
2. deleteMany() 메서드를 실행하여 ID가 "duplicateid"인 모든 행을 제거합니다.
db.User.deleteMany({_id:"duplicateid"});
3. User 개체에 저장된 값을 삽입합니다.
db.User.insertOne(User);
쉽고 빠르게!!!!
먼저 DB에서 모든 중복 항목을 찾아 해당 중복 항목을 제거할 수 있습니다.여기서는 중복 항목을 확인하고 제거하기 위해 ID 열을 사용합니다.
db.collection.aggregate([
{ "$group": { "_id": "$id", "count": { "$sum": 1 } } },
{ "$match": { "_id": { "$ne": null }, "count": { "$gt": 1 } } },
{ "$sort": { "count": -1 } },
{ "$project": { "name": "$_id", "_id": 0 } }
]).then(data => {
var dr = data.map(d => d.name);
console.log("duplicate Recods:: ", dr);
db.collection.remove({ id: { $in: dr } }).then(removedD => {
console.log("Removed duplicate Data:: ", removedD);
})
})
일반적인 아이디어는 findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ 을 사용하여 컬렉션의 중복 레코드에서 하나의 임의 ID를 검색하는 것입니다.
findOne 옵션에서 검색한 random-id 이외의 모든 레코드를 삭제합니다.
만약 당신이 피몽고에서 그것을 하려고 한다면 당신은 이것과 같은 것을 할 수 있습니다.
def _run_query():
try:
for record in (aggregate_based_on_field(collection)):
if not record:
continue
_logger.info("Working on Record %s", record)
try:
retain = db.collection.find_one(find_one({'fie1d1': 'x', 'field2':'y'}, {'_id': 1}))
_logger.info("_id to retain from duplicates %s", retain['_id'])
db.collection.remove({'fie1d1': 'x', 'field2':'y', '_id': {'$ne': retain['_id']}})
except Exception as ex:
_logger.error(" Error when retaining the record :%s Exception: %s", x, str(ex))
except Exception as e:
_logger.error("Mongo error when deleting duplicates %s", str(e))
def aggregate_based_on_field(collection):
return collection.aggregate([{'$group' : {'_id': "$fieldX"}}])
셸에서:
- find_one을 findOne으로 바꿉니다.
- 동일한 제거 명령이 작동해야 합니다.
저는 3M 중복 레코드를 제거해야 했고 Mongo shell에서 다음과 같은 작업을 했습니다.
use your_DB_name
for (var i = 1; i <= 30; i++) { var data = db.collectionName.aggregate([ {"$group" : { "_id":"$yourGroupById" , "count": { "$sum": 1 },"data": { "$push": "$$ROOT" }}}, { $project: { result: { $slice: [ "$data", { $subtract: [{ $size: "$data" }, 1] } ] }, count:"$count" } },{"$unwind": "$result"},{ $limit : 100000 } ],{allowDiskUse:true}).toArray() data = data.map(r=>r.result._id) db.collectionName.deleteMany({_id:{$in:data}}) data = [] }
언급URL : https://stackoverflow.com/questions/14184099/fastest-way-to-remove-duplicate-documents-in-mongodb
'IT' 카테고리의 다른 글
Ruby File.open 모드 및 옵션은 무엇입니까? (0) | 2023.06.02 |
---|---|
WPF 데이터 그리드의 텍스트 정렬 (0) | 2023.06.02 |
@Java로 주석 문서화 (0) | 2023.06.02 |
iPhone 일정관리에서 사용자 지정 이벤트를 프로그래밍 방식으로 추가 (0) | 2023.06.02 |
#N/A 또는 빈 셀을 무시하는 차트를 Excel에서 생성 (0) | 2023.06.02 |