【问题标题】:MongoDB using mongoose driver: how to query on a subset of document's array property?MongoDB 使用 mongoose 驱动程序:如何查询文档数组属性的子集?
【发布时间】:2015-10-22 23:27:39
【问题描述】:

我收集了选民登记数据。它们采用以下形式:

voter = {
  name: "Some name",
  registrationDate: "2015-10-21T15:41:36+00:00",
  votingHistory: ["2015-10-21T15:41:36+00:00", "2015-7-21T15:41:36+00:00"]
}

我需要能够检测到votingHistory 数组子集的大小。例如,这是我尝试过的查询之一:

voters.find({
  votingHistory : { $all : {$size : { $gt : 8 }}, { $gt : "2015-7-21T15:41:36+00:00" }}
})

此查询的目的是查找 2015 年 7 月 21 日之后至少有 8 票记录投票的所有选民。 mongoose 有没有办法查询数组属性子集的大小?

例如,具有以下三个条目:

{
  name: "name1",
  VotingHistory: ["2015-10-21T15:41:36+00:00", "2013-7-21T15:41:36+00:00"]
}, 
{
  name: "name2",
  VotingHistory: ["2015-10-21T15:41:36+00:00", "2011-7-21T15:41:36+00:00"]
}, 
{
  name: "name3",
  VotingHistory: ["2013-10-21T15:41:36+00:00", "2011-7-21T15:41:36+00:00", "2009-10-21T15:41:36+00:00", "2010-7-21T15:41:36+00:00"]
}

我想找出 VotingHistory 数组中的 2 个或更多元素代表 2013 年 7 月 21 日或之后的日期。在这个例子中只有名字 1。

【问题讨论】:

    标签: node.js mongodb mongoose mongodb-query aggregation-framework


    【解决方案1】:

    为了有效查询,我建议通过将表示日期的字符串文字更改为实际日期来修改您的猫鼬模式。

    您可以先在架构定义处修改它,例如

    var mongoose = require('mongoose');
    var Schema = mongoose.Schema;
    
    var voterSchema = new Schema({
        name: String,
        registrationDate: Date,
        votingHistory: [Date]
    });
    
    var Voter = mongoose.model("Voter", voterSchema, "voters" );
    

    完成后,您需要使用 Bulk Operations API 修改现有集合,以利用您的更新。对于任何给定的 mongoose 模型,都存在一个 .collection 访问器,它本质上从实现 mongoose 的底层“节点本机驱动程序”访问“集合对象”。有了这个,您可以对 registrationDate 字段是字符串的文档进行以下更新

    mongoose.connection.on("open", function(err, conn) { 
    
        var bulk = Voter.collection.initializeOrderedBulkOp();
        var counter = 0;
    
        Voter.find({"registrationDate": {"$type": 2} }, function(err, docs) {
            async.each(docs, function(doc, callback) {
                var regDate = new Date(doc.registrationDate),
                    history = doc.votingHistory.map(function (dt){
                        return new Date(dt);
                    });
                bulk.find({"_id": doc._id}).updateOne({
                    "$set": {
                        registrationDate: regDate,
                        votingHistory: history
                    }
                });
                counter++;
    
                if (counter % 1000 == 0) {
                    bulk.execute(function(err,result) {             
                        bulk = Voter.collection.initializeOrderedBulkOp();
                    });
                } 
                else {
                    callback();
                }
            },
    
            // When everything's done
            function(err) {
                if ( counter % 1000 != 0 ) 
                    bulk.execute(function(err,result) {
                       console.log("more updates" );
                    });        
                console.log("done now");
            }
        });
    });
    

    更新完成后,您可以执行以下两种方法中的任何一种。 其中之一是使用 $where 运算符:

    var voteDate = new Date(2015, 6, 21);
    Voter.find({ 
        "$where": "this.votingHistory.length > 8",
        "votingHistory": { "$gt": voteDate  }
    }).exec(callback);
    

    另一种是使用 dot notation 来“欺骗” mongodb 以查找至少具有第 9 个 votingHistory 数组元素的文档:

    var voteDate = new Date(2015, 6, 21);
    Voter.find({ 
        "votingHistory.8": { "$exists": true },
        "votingHistory": { "$gt": voteDate }
    }).exec(callback);
    

    对于基于 aggregation framework 的解决方案(基于日期是正确的 MongoDB 日期的假设),以下管道将为您提供所需的结果:

    var voteDate = new Date(2015, 6, 21),
        pipeline = [
            {
                "$project": {
                    "name": 1, 
                    "registrationDate": 1,
                    "votingHistory": 1,
                    "numberOfVotes": { "$size": "$votingHistory" }
                }
            },
            {
                "$match": {
                    "numberOfVotes": { "$gt": 8 },
                    "votingHistory": { "$gt": voteDate }
                }
            }
    
        ];
    // you can then use the aggregate   
    Voter.aggregate(pipeline)
         .exec(function (err, results){
            // access your results here
         });
    
    // or using the aggregation builder
    Voter.aggregate()
        .project({
            "name": 1, 
            "registrationDate": 1,
            "votingHistory": 1,
            "numberOfVotes": { "$size": "$votingHistory" }
        })
        .match({
            "numberOfVotes": { "$gt": 8 },
            "votingHistory": { "$gt": voteDate }
        })
        .exec(callback);
    

    【讨论】:

    • 我重构了日期,但您提供的搜索都没有提供所需的行为。
    • 您能否编辑您的问题以提供一些具有预期结果的示例数据?
    • 它已被编辑。感谢您的广泛帮助。我可以操纵数据,以便每个选举日期都是它自己的子文档。如果数据采用这种格式,我想我知道该怎么做。
    【解决方案2】:

    如果你愿意像下面这样使用 mongo 聚合是可能的

    db.voter.aggregate([
        {$unwind:"$votingHistory"},
    { $match: {votingHistory:{$gt:'2015-7-21T15:41:36+00:00'}}}, 
    {$group:{_id: {voterId:'$_id',name:'$name',registrationDate:'$registrationDate'}, count:{$sum:1}}},
    {$match:{count:{$gt:2}}},
    {$project:{_id:"$_id.voterId", name:"$_id.name", registrationDate:"$_id.registrationDate"}}
    ])
    

    【讨论】:

    • 尽管您的示例代码返回了一个长的 _pipeline 对象,但我不知道如何使用它,但我会赞成您的回答,因为您将我指向正确的方向。如果我能弄清楚如何实际使用聚合来解决我的问题,我会接受你的回答。
    • 当然。如果我知道您的预期输出,我可以尝试提供帮助。
    • @SriniKandula OP 正在使用异步 node.js 和 Mongoose,但您的答案是使用同步 shell。我想这就是让他绊倒的原因。
    • @JohnnyHK 你是对的,我错过了。 Mongo shell 和 mongoose 的语法非常接近。
    • 我不得不对你的答案投反对票,因为我专门要求使用猫鼬的解决方案。对不起!
    猜你喜欢
    • 2013-09-16
    • 2012-05-15
    • 1970-01-01
    • 2012-11-02
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    相关资源
    最近更新 更多