Mongoose找到一个并推送到一系列文档

时间:2015-03-09 22:02:50

标签: javascript node.js mongodb mongoose mongodb-query

我是MongoDB和Mongoose的新手,我正在尝试使用它来保存日间交易分析的股票价格。所以我想象了这个架构:

symbolSchema = Schema({
    name:String,
    code:String
});

quoteSchema = Schema({
    date:{type:Date, default: now},
    open:Number, 
    high:Number,
    low:Number,
    close:Number,
    volume:Number
});

intradayQuotesSchema = Schema({
    id_symbol:{type:Schema.Types.ObjectId, ref:"symbol"},
    day:Date,
    quotes:[quotesSchema]
});

从我的链接中,我每分钟都收到这样的信息:

日期|符号|打开|高|低|关闭|体积

2015-03-09 13:23:00 | AAPL | 127,14 | 127,17 | 127,12 | 127,15 | 19734

我必须:

  1. 找到符号的ObjectId(AAPL)。
  2. 发现此符号的intradayQuote文档是否已存在(符号和日期组合)
  3. 发现此符号的分钟OHLCV数据是否存在于引号数组中(因为它可以重复)
  4. 更新或创建文档并更新或在数组
  5. 中创建引号

    如果引号已存在,我可以完成此任务,但是这个方法可以在引号数组中创建重复的条目:

    symbol.find({"code":mySymbol}, function(err, stock) {
        intradayQuote.findOneAndUpdate({
            { id_symbol:stock[0]._id, day: myDay },
            { $push: { quotes: myQuotes } },
            { upsert: true },
            myCallback
        });
    });
    

    我已经尝试过:

    • $ addToSet 而不是$ push,但不幸的是,这似乎不适用于文档数组
    • {id_symbol:stock [0] ._ id,day:myDay,'quotes [“date”]':myDate} ,条件为 findOneAndUpdate ;但不幸的是,如果mongo找不到它,它会为分钟创建一个新文档,而不是附加到引号数组。

    有没有办法让这个工作不用再多一个查询(我已经使用2)?我应该重新考虑我的架构以促进这项工作吗?任何帮助将不胜感激。谢谢!

1 个答案:

答案 0 :(得分:2)

基本上,$addToSet运算符无法为您工作,因为根据定义,您的数据不是真正的"set"是“完全不同”对象的集合。

这里的另一个逻辑意义是,当数据到达时,您将处理数据,无论是作为sinlge对象还是feed。我会假设它以某种形式提供了许多项目,你可以使用某种流处理器来获得每个文件的结构:

{
    "date": new Date("2015-03-09 13:23:00.000Z"),
    "symbol": "AAPL",
    "open": 127.14
    "high": 127.17,
    "low": 127.12 
    "close": 127.15,
    "volume": 19734
}

转换为标准十进制格式以及UTC日期,因为当从数据存储区中检索数据时,任何区域设置确实应该是应用程序的域。

我还会通过删除对其他集合的引用并将数据放在那里来至少平掉你的“intraDayQuoteSchema”。你仍然需要在插入时查找,但是读取时额外填充的开销似乎比存储开销更昂贵:

intradayQuotesSchema = Schema({
    symbol:{
        name: String,
        code: String
    },
    day:Date,
    quotes:[quotesSchema]
});

这取决于你的使用模式,但它可能会更有效。

其余部分真正归结为

可接受的内容
stream.on(function(data) {

    var symbol = data.symbol,
        myDay = new Date( 
            data.date.valueOf() - 
                ( data.date.valueOf() % 1000 * 60 * 60 * 24 ));
    delete data.symbol;

    symbol.findOne({ "code": symbol },function(err,stock) {

        intraDayQuote.findOneAndUpdate(
            { "symbol.code": symbol , "day": myDay },
            { "$setOnInsert": { 
               "symbol.name": stock.name
               "quotes": [data] 
            }},
            { "upsert": true }
            function(err,doc) {
                intraDayQuote.findOneAndUpdate(
                    {
                        "symbol.code": symbol,
                        "day": myDay,
                        "quotes.date": data.date
                    },
                    { "$set": { "quotes.$": data } },
                    function(err,doc) {
                        intraDayQuote.findOneAndUpdate(
                            {
                                "symbol.code": symbol,
                                "day": myDay,
                                "quotes.date": { "$ne": data.date }
                            },
                            { "$push": { "quotes": data } },
                            function(err,doc) {

                            }
                       );    
                    }
                );
            }
        );    
    });
});

如果您在响应中实际上不需要修改后的文档,那么您可以通过在此处实施批量操作API并在单个数据库请求中发送此包中的所有更新来获得一些好处:

stream.on("data",function(data) {

    var symbol = data.symbol,
        myDay = new Date( 
            data.date.valueOf() - 
                ( data.date.valueOf() % 1000 * 60 * 60 * 24 ));
    delete data.symbol;

     symbol.findOne({ "code": symbol },function(err,stock) {
         var bulk = intraDayQuote.collection.initializeOrderedBulkOp();
         bulk.find({ "symbol.code": symbol , "day": myDay })
             .upsert().updateOne({
                 "$setOnInsert": { 
                     "symbol.name": stock.name
                     "quotes": [data] 
                 }
             });

         bulk.find({
             "symbol.code": symbol,
             "day": myDay,
             "quotes.date": data.date
         }).updateOne({
             "$set": { "quotes.$": data }
         });

         bulk.find({
             "symbol.code": symbol,
             "day": myDay,
             "quotes.date": { "$ne": data.date }
         }).updateOne({
             "$push": { "quotes": data }
         });

         bulk.execute(function(err,result) {
             // maybe do something with the response
         });            
     });
});

关键是,那里只有一个语句会实际修改数据,因为这些都是在同一个请求中发送的,所以在应用程序和服务器之间来回减少。

另一种情况是,在这种情况下,在另一个集合中引用实际数据可能更简单。这只是处理upserts的一个简单问题:

intradayQuotesSchema = Schema({
    symbol:{
        name: String,
        code: String
    },
    day:Date,
    quotes:[{ type: Schema.Types.ObjectId, ref: "quote" }]
});


// and in the steam processor

stream.on("data",function(data) {

    var symbol = data.symbol,
        myDay = new Date( 
            data.date.valueOf() - 
                ( data.date.valueOf() % 1000 * 60 * 60 * 24 ));
    delete data.symbol;

    symbol.findOne({ "code": symbol },function(err,stock) {
         quote.update(
            { "date": data.date },
            { "$setOnInsert": data },
            { "upsert": true },
            function(err,num,raw) {
                if ( !raw.updatedExisting ) {
                    intraDayQuote.update(
                        { "symbol.code": symbol , "day": myDay },
                        { 
                            "$setOnInsert": {
                                "symbol.name": stock.name
                            },
                            "$addToSet": { "quotes": data }
                        },
                        { "upsert": true },
                        function(err,num,raw) {

                        }
                    );
                }
            }
        );
    });
});

归结为对于将“嵌入”日期文档中的引号数据放在一起有多重要。主要区别在于,如果要根据数据查询这些文档中的某些“引用”字段,或者使用.populate()从其他集合中引入“引号”的开销。

当然,如果引用和引用数据对您的查询过滤很重要,那么您始终可以只查询该_id值匹配的集合,并在“日期”使用$in查询文档仅匹配包含那些匹配的“引用”文档的日期。

根据应用程序使用数据的方式,最重要的是哪个路径最重要。希望这应该指导你做你想要实现的目标背后的一般概念。

PS除非您“确定”您的源数据始终是一个四舍五入到精确“分钟”的日期,否则您可能希望使用与用于获得离散“日期”相同类型的日期舍入数学。