根据密钥删除重复项并在Mongodb中引用对象?

时间:2015-05-01 14:28:32

标签: mongodb dictionary mongoose reduce

我有演员和电影的MongoDB模型。这两个模型的Mongoose模式如下:

var ActorsSchema = new Schema({
    id : {
        type : Number
    },
    known_for:[{
        type: Schema.Types.ObjectId,
        ref: 'Movie'
    }]
})

var MovieSchema = new Schema({
    genres: [{
        type: Schema.Types.ObjectId,
        ref: 'Genre'
    }],
    id: {
        type: Number
    }
});
actor模型中的

known_for 属性包含对该actor已加星标的电影列表的引用。

我想删除重复的Actor记录,这些记录将使用id fieled(而不是_id)来确定。但我还想做的是删除已知删除的actor中记录在known_for字段中的电影也要删除,我想从Mongo界面那样做,因为这些文档中的记录数量非常大并且执行此功能以编程方式将是时间效率低下。

我查看了相关的question,但它不适用于将其他模型作为字段引用的模型。

1 个答案:

答案 0 :(得分:1)

考虑使用聚合框架来识别重复文档,获取actors集合的重复_ids列表以及movie ID数组,并使用ids数组作为查询发出删除和更新命令。 / p>

出于测试目的,假设您的馆藏中包含以下数据(最少的测试用例,当然是为了演示目的):

db.movies.insert([
    {
        "_id" : ObjectId("5543e79e42063d2be5d2ea84"),
        "id" : 1,
        "genres" : []
    },
    {
        "_id" : ObjectId("5543e79e42063d2be5d2ea85"),
        "id" : 2,
        "genres" : []
    },
    {
        "_id" : ObjectId("5543e79e42063d2be5d2ea86"),
        "id" : 3,
        "genres" : []
    }
]);

db.actors.insert([
    { id: 1, known_for: [ObjectId("5543e79e42063d2be5d2ea84")] },
    { id: 1, known_for: [ObjectId("5543e79e42063d2be5d2ea84")] },
    { id: 2, known_for: [ObjectId("5543e79e42063d2be5d2ea84"), ObjectId("5543e79e42063d2be5d2ea85")] },
    { id: 3, known_for: [ObjectId("5543e79e42063d2be5d2ea85"), ObjectId("5543e79e42063d2be5d2ea86")] }
]);

现在为神奇的部分。聚合管道按id对actor文档进行分组,计算分组计数,创建两个数组字段,用于保存actor _id重复项和电影对象id。管道将结果输出到 dupes 集合,稍后将用于删除重复项:

db.actors.aggregate([
    {
        "$group": {
            "_id": "$id",
            "duplicates": { "$addToSet": "$_id" },
            "movies": { "$addToSet": "$known_for"},
            "count": { "$sum": 1 }
        }
    },
    {
        "$match": {
            "count": { "$gt": 1 }
        }
    },
    {
        "$out": "dupes"
    }
])

查询dupes集合将得到结果:

/* 1 */
{
    "_id" : 1.0000000000000000,
    "duplicates" : [ 
        ObjectId("5543fc8e42063d2be5d2eaa2"), 
        ObjectId("5543fc8e42063d2be5d2eaa1")
    ],
    "movies" : [ 
        [ 
            ObjectId("5543e79e42063d2be5d2ea84")
        ]
    ],
    "count" : 2
}

现在为有趣的部分。使用 dupes 集合,然后从actors集合中删除dupes。正如您从dupes集合中注意到的那样,电影字段是一个数组数组,因此您需要将其展平并使用展平数组然后删除电影并从演员中提取孤立的电影参考系列:

db.dupes.find({}).find({}).forEach( function (doc) {
    var movie_dupes = [];    
    db.actors.remove({ "_id": { "$in": doc.duplicates } });    

    doc.movies.forEach( function (arr){
        arr.forEach(function (id){
            movie_dupes.push(id)
        });    
    });
    db.movies.remove({ "_id": { "$in": movie_dupes } });
    db.actors.update({ "known_for": { "$in": movie_dupes } }, { "$pull": { "known_for": { "$in": movie_dupes } } }, { "multi": true });    

});

登录到控制台:

Removed 2 record(s) in 38ms
Removed 1 record(s) in 2ms
Updated 1 existing record(s) in 1ms

现在验证我们的副本是否已被删除:

db.actors.find()

/* 1 */
{
    "_id" : ObjectId("5543fc8e42063d2be5d2eaa3"),
    "id" : 2,
    "known_for" : [ 
        ObjectId("5543e79e42063d2be5d2ea85")
    ]
}

/* 2 */
{
    "_id" : ObjectId("5543fc8e42063d2be5d2eaa4"),
    "id" : 3,
    "known_for" : [ 
        ObjectId("5543e79e42063d2be5d2ea85"), 
        ObjectId("5543e79e42063d2be5d2ea86")
    ]
}

id 1(这是重复的)的演员确实已被删除。

db.movies.find()

/* 1 */
{
    "_id" : ObjectId("5543e79e42063d2be5d2ea85"),
    "id" : 2,
    "genres" : []
}

/* 2 */
{
    "_id" : ObjectId("5543e79e42063d2be5d2ea86"),
    "id" : 3,
    "genres" : []
}

ObjectId("5543e79e42063d2be5d2ea84")的电影已被删除。