【问题标题】:Mongoose find one and push to array of documentsMongoose 找到一个并推送到文档数组
【发布时间】:2015-05-11 05:36:11
【问题描述】:

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

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. 更新或创建文档并更新或创建数组内的引号

如果引号已经存在,我可以在不验证的情况下完成此任务,但是此方法可以在引号数组中创建重复条目:

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 个)?我应该重新考虑我的架构以促进这项工作吗?任何帮助将不胜感激。谢谢!

【问题讨论】:

  • 对不起,但这里并不清楚您需要在数组中的现有项目中“更新”什么,以及除“唯一”约束之外的其他字段如何应用于这些更新。否 $addToSet 将不起作用,因为“部分不同键”不构成不同对象的“集合”。所以你需要多个查询。在不知道如何应用更新的情况下,实际模式尚不清楚。
  • 对不起,如果我的解释令人困惑。我相信这很复杂,甚至不可能。我需要更新日期等于我尝试更新的日期的数组文档项(在 intradayQuote 模型中,其 ObjectId 等于我的符号代码的符号 _id)。如果我能匹配这些条件,我将更新数组中的整个文档(所有键:打开、高......)。如果我不能匹配,我应该在数组中创建一个新文档。如果 intradayQuote 文档也不存在,我应该在创建数组项之前创建它。
  • 如果已经有那个不同的“分钟”字段,那么替换整个数组元素对吗?这就是我要问的。我在这里也看不到外部查询的意义。如果您循环任何内容,它似乎是您正在显示的格式的“引号”列表,然后转换为您的模式模式。这就是这段代码真正想要做的吗?为什么嵌入式阵列很重要?因此,您如何实际使用数据,否则会使其成为优势?您的呈现方式没有错,但将任务分解为不同的问题可能会更好地为您服务
  • 没错。感谢您的耐心。实际上我不确定我的架构。而且我还不知道我将如何使用这些数据。嵌入数组的要点是:有很多品种,每个品种有大约 540 个每日报价。所以我想每天为每个符号保存一个新文档。每天我通常需要每个交易品种的最后 2 或 3 天的报价来进行主要分析(但我肯定需要长期的历史价格)。所以我打破了不同文件中的每日报价以获得良好的表现。但我是 MongoDB 新手,也许有更好的方法。

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


【解决方案1】:

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

这里的另一个逻辑意义是,您将在数据到达时对其进行处理,无论是作为单一对象还是作为提要。我假设它以某种形式包含许多项目,并且您可以使用某种流处理器来达到每个收到的文档的这种结构:

{
    "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) {

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

这真的归结为将引号数据嵌套在“day”文档中对您来说有多重要。主要区别在于,如果您想根据其中一些“引用”字段的数据查询这些文档,或者要忍受使用 .populate() 从其他集合中提取“引用”的开销。

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

根据您的应用程序使用数据的方式,选择哪条路径最为重要,这是一个重大决定。希望这可以指导您了解实现您想要实现的目标背后的一般概念。

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

【讨论】:

  • 哇!非常感谢!你给了我很多尝试的想法! :) 当然这会帮助我。一些细节:1)符号代码有时会发生变化,因此,我将符号保存在一个单独的文档中,用作参考(这样我就可以在不丢失参考的情况下更改代码)。 2) 我不确定我是否理解在 $set 之后使用 $push 的原因。 $set 尝试更新旧条目,如果条目不存在,$push 会尝试推送,对吗?但在我看来,如果条件不满足,这个 $push 将创建一个全新的文档。我错了吗? 3) "quotes.$" 是如何工作的?
  • @LéoMuniz 1. 如果您想要参考,请保留它,因为它只是基于所呈现内容的推荐观察。 2.基本思路在查询选择。如果项目存在则 $set,如果不存在则 $push。根据查询条件,两者不可能匹配。 3. 它是positional $ 运算符。它匹配数组中“匹配”元素的位置并从该位置更新。花点时间消化并阅读所有链接。
  • 太棒了!再次感谢你。现在我会做我的工作并尝试解决我的问题。你帮了我很多。当我有解决方案时,我会告诉你。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多