2013-01-06 35 views
27

Tôi có khoảng 1,7 triệu tài liệu trong mongodb (trong tương lai 10m +). Một số người trong số họ đại diện cho mục trùng lặp mà tôi không muốn. Cấu trúc của tài liệu là một cái gì đó như thế này:Cách nhanh nhất để xóa tài liệu trùng lặp trong mongodb

{ 
    _id: 14124412, 
    nodes: [ 
     12345, 
     54321 
     ], 
    name: "Some beauty" 
} 

Tài liệu được lặp lại nếu nó có ở nhất một nút cùng như tài liệu khác với cùng tên. Cách nhanh nhất để loại bỏ các bản sao là gì?

Trả lời

37

Giả sử bạn muốn xóa vĩnh viễn các tài liệu có chứa một bản sao name + nodes nhập từ bộ sưu tập, bạn có thể thêm một chỉ số unique với các tùy chọn dropDups: true:

db.test.ensureIndex({name: 1, nodes: 1}, {unique: true, dropDups: true}) 

Như các tài liệu nói, sử dụng hết sức thận trọng với điều này vì nó sẽ xóa dữ liệu khỏi cơ sở dữ liệu của bạn. Sao lưu cơ sở dữ liệu của bạn trước tiên trong trường hợp nó không thực hiện chính xác như bạn mong đợi.

CẬP NHẬT

Giải pháp này chỉ có giá trị thông qua MongoDB 2.x như tùy chọn dropDups không còn có sẵn trong 3.0 (docs).

+0

Tên không nhất thiết phải là duy nhất. Điều này sẽ xóa nó chỉ khi cả hai tên và ít nhất một nút là giống nhau? – ewooycom

+4

@ user1188570 Nó là hợp chất vì vậy cả hai lĩnh vực phải có một bản sao trong cùng một tài liệu – Sammaye

+0

@Sammaye Tôi nghĩ rằng giải pháp tốt hơn để hợp nhất các nút, có bất cứ điều gì giống như hành động: {$ merge: nodes} thay vì dropDups? Làm thế nào bạn sẽ đạt được điều này? – ewooycom

44

dropDups: true tùy chọn không khả dụng ở 3.0.

Tôi có giải pháp với khung tổng hợp để thu thập các bản sao và sau đó xóa trong một lần.

Nó có thể hơi chậm hơn so với thay đổi "chỉ mục" cấp hệ thống. Nhưng nó là tốt bằng cách xem xét cách bạn muốn loại bỏ các tài liệu trùng lặp.

a. Xóa tất cả tài liệu trong một lần

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}}) 

b. Bạn có thể xóa từng tài liệu một.

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 
}) 
+0

Nếu kết quả không được sử dụng có nghĩa là, tôi đã nhận được sự cố TypeError – sara

+1

Cảm ơn bạn đã trả lời hữu ích! Tôi đã tìm thấy nó tốt hơn, khi bạn có nhiều hàng (tôi đã 5M hàng) tốt hơn nên tạo bộ đếm và giới hạn nó cho mỗi 10K và không phải cho toàn bộ bản sao vì nó có thể quá lớn :) – Mazki516

+0

Điều này có vẻ tuyệt vời! Bạn có đề xuất về hiệu suất nào không? Tôi có khoảng 3M hàng với vài dups. Có tốt hơn để làm trong một lần (giải pháp của bạn a), hoặc từng cái một? – Nico

12

Tạo bãi bộ sưu tập với mongodump

Rõ ràng bộ sưu tập

Thêm chỉ số duy nhất

Khôi phục bộ sưu tập với mongorestore

+0

Đây là cách dễ nhất để tôi thực hiện việc này - một vài phút của thời gian chết để tiết kiệm căng thẳng của việc phải chạy một truy vấn không quen thuộc. – misaka

+0

Đây là cách dễ dàng và trực quan hơn để thực hiện. Cảm ơn. – Nerzid

+0

Cảm ơn, tôi có thể làm rõ rằng việc khôi phục bộ sưu tập sau khi thêm một chỉ mục duy nhất có nghĩa là sẽ không có lỗi khi một mục trùng lặp được thử? – memebrain

5

tôi tìm thấy giải pháp này làm việc với MongoDB 3.4: tôi sẽ giả định trường có trùng lặp được gọi là 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}) 

Mới làm quen với mongoDB, tôi đã dành rất nhiều thời gian và sử dụng các giải pháp dài khác để tìm và xóa các bản sao. Tuy nhiên, tôi nghĩ giải pháp này gọn gàng và dễ hiểu.

Nó hoạt động bằng các tài liệu khớp đầu tiên có chứa fieldX (Tôi đã có một số tài liệu không có trường này và tôi có thêm một kết quả trống).

Nhóm giai đoạn tiếp theo là tài liệu theo trườngX và chỉ chèn tài liệu $first vào mỗi nhóm sử dụng $$ROOT.Cuối cùng, nó thay thế toàn bộ nhóm tổng hợp bằng tài liệu được tìm thấy bằng $ ROOT đầu tiên và $$.

Tôi phải thêm allowDiskUse vì bộ sưu tập của tôi lớn.

Bạn có thể thêm số này sau bất kỳ số lượng đường ống nào và mặc dù tài liệu cho $ first đề cập đến một giai đoạn sắp xếp trước khi sử dụng $ đầu tiên, nó làm việc cho tôi mà không có nó. "Couldnt đăng một liên kết ở đây, uy tín của tôi là ít hơn 10 :("

Bạn có thể lưu kết quả cho một bộ sưu tập mới bằng cách thêm một giai đoạn $ ra ...

Ngoài ra, nếu người duy nhất là quan tâm đến một vài lĩnh vực ví dụ field1, field2, và không phải là toàn bộ tài liệu, ở vòng bảng mà không replaceRoot:

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}) 
0

Dưới đây là một cách 'thủ công' nhẹ hơn để làm việc đó:

về cơ bản, đầu tiên , được danh sách tất cả các khóa duy nhất mà bạn quan tâm.

Sau đó thực hiện tìm kiếm bằng cách sử dụng từng khóa đó và xóa nếu tìm kiếm đó trả về lớn hơn một.

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++ 
    }) 
    }); 
0
  1. ý tưởng chung là sử dụng findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ để lấy một id ngẫu nhiên từ các hồ sơ trùng lặp trong bộ sưu tập.

  2. Xóa tất cả các bản ghi trong bộ sưu tập khác với id ngẫu nhiên mà chúng tôi đã truy lục từ tùy chọn findOne.

Bạn có thể làm điều gì đó như thế này nếu bạn đang cố gắng làm điều đó trong pymongo.

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"}}]) 

Từ vỏ:

  1. Thay find_one để findOne
  2. Cùng lệnh remove nên làm việc.
Các vấn đề liên quan