【问题标题】:Rails 3 Calculate age of embedded recordsRails 3计算嵌入记录的年龄
【发布时间】:2023-03-14 08:15:01
【问题描述】:

我正在使用 mongodb,并且有一个将 cmets 添加为嵌入文档的模型。

如何获取参赛作品的 cmets 平均年龄? (相对示例,我的字段略有不同)

所以我可以有多个 cmets 用于一个条目,我需要找出评论的平均年龄,或平均 :cal_date。对于所有条目/cmets 或每个条目,可以收集其他指标,例如最大 :cal_date...

这有意义吗?需要更多细节?我很高兴有义务得到解决方案。一段时间以来,我一直对日期计算感到困惑。

另一种思考方式是使用图书馆图书模型:有很多书,每本书都有很多借出/借入。我需要找出每本书的平均检出时间所有书籍的平均检出时间。同样,只是指标,但这些都是日期的事实令人困惑。

{
  _id: ObjectId("51b0d94c3f72fb89c9000014"),
  barcode: "H-131887",
  comments: [
    {
      _id: ObjectId("51b0d94c3f72fb89c9000015"),
      cal_date: ISODate("2013-07-03T16:04:57.893Z"),
      cal_date_due: ISODate("2013-07-03T16:04:57.894Z")
    },
    {
      _id: ObjectId("51b0e6053f72fbb27900001b"),
      cal_date: ISODate("2012-07-03T19:39:43.074Z"),
      cal_date_due: ISODate("2013-07-03T19:39:43.076Z"),
      updated_at: ISODate("2013-06-06T19:41:57.770Z"),
      created_at: ISODate("2013-06-06T19:41:57.770Z")
    }
  ],
  created_at: ISODate("2013-06-06T18:47:40.481Z"),
  creator_id: ObjectId("5170547c791e4b1a16000001"),
  description: "",
  maker: "MITUTOYO",
  model: "2046S",
  serial: "QEL228",
  status: "Out",
  updated_at: ISODate("2013-06-07T18:54:38.340Z")
}

还有一件事 如何使用 $push 在输出中包含其他字段?我可以让它工作,但它包括,比如条形码,数组中的两次"barcode" => ["H-131887", "H-131887"]

【问题讨论】:

  • 添加书籍内容只会让事情变得混乱 - 你有 cmets 数组,并且你想知道每篇博文中所有 cmets 的平均“年龄” - 似乎很清楚。
  • 你能澄清一下你在哪一部分遇到了问题:从 Ruby 运行聚合框架还是在 Ruby 中找出正确的聚合框架管道语法?
  • 在您的示例中,哪个字段是“退房”日期?是“cmets.cal_date”吗?哪个是“签到”- cal_date_due 可能是这本书到期的时间,但如果那是过去,它是否设置为归还日期?您需要解释示例文档中的不同字段代表什么,因为如果没有更多信息,您在其中的特定日期对我没有任何意义。
  • map/reduce 不是必需的,因为您没有提到聚合框架无法完成的任何事情,而且聚合框架比 map/reduce 更快(更简单)。
  • 如果你想做 $push 但消除重复,你会使用 $addToSet。但是,如果您只是想恢复原始条形码值,而不是创建数组,我建议使用 $first 表达式。我将更新显示此内容的示例解决方案。

标签: ruby-on-rails mongodb date-math


【解决方案1】:

您没有说您希望使用什么时间单位,但我只是要向您展示如何在几分钟内恢复它,并相信您可以计算出如何将其转换为任何其他时间粒度。我将假设原始文档具有这样的架构:

{ _id: xxx,
  post_id: uniqueId,
  comments: [ { ..., date: ISODate() }, ..., { ... , date: ISODate() } ],
  ...
}

现在聚合:

// first you want to define some fixed point in time that you are calculating age from.
// I'm going to use a moment just before "now"
var now = new Date()-1
// unwind the comments array so you can work with individual comments
var unwind = {$unwind:"$comments"};
// calculate a new comment_age value
var project = {$project: {
       post_id:1, 
       comment_age: {
           $divide:[ 
               {$subtract:[now, "$comments.date"]},
               60000
           ]
       }
} };
// group back by post_id calculating average age of comments
var group = {$group: {
               _id: "$post_id",
               age: {$avg: "$comment_age"}
            } };
// now do the aggregation:

db.coll.aggregate( unwind, project, group )

您可以使用 $max、$min 和其他分组功能来查找最早和最新的评论日期或最低/最高评论年龄。您可以按 post_id 分组,也可以按常量分组以查找整个集合的这些计算等。

* 编辑 * 以您为“图书馆书”包含的文档为例,这可能是计算当前“出”的每本书的出库时间的管道,假设“cmets.cal_date”是它被检出的时间并且所有 cmets 的最新 cal_date 代表当前的“结帐”(旧的已返回):

 db.coll.aggregate( [
    { $match  : { status : "Out"  } },
    { $unwind : "$comments" },
    { $group  : { _id : "$_id", 
                  cal_date : { $max : "$comments.cal_date" } 
                } 
    },
    { $project : { outDuration : { $divide : [ 
                                     { $subtract : [ 
                                                     ISODate("2013-07-15"), 
                                                     "$cal_date" 
                                                   ] 
                                     },
                                     24*60*60*1000 
                                    ] 
                                  }
                  } 
    },
    { $group : { _id : 1, 
                 avgOut : { $avg : "$outDuration" } 
               } 
    } 
 ] )

步骤在做什么:

  • 根据status 过滤掉文档,以计算当前仅Out 的书籍。
  • $unwind 展平“cmets”数组,以便我们可以
  • 使用$group$max 查找最新的cal_date 条目。
  • 使用此最大 cal_date(表示图书的借出时间)从今天的日期中减去它,然后将结果除以一天中的毫秒数,得到这本书已借出的天数
  • $group所有结果一起求出所有已借书的平均天数。

* 编辑 * 我假设您了解 Ruby,并且只需要知道如何执行聚合框架命令来计算日期差异/平均值/等。这是在 Ruby 中使用“now”来比较 cal_date 的相同代码(您也可以使用常量日期值:

# get db collection from MongoClient into variable 'coll'
# see basic MongoDB Ruby driver tutorial for details
coll.aggregate([ 
   { "$match"  => {"status"=>"Out"} }, 
   { "$unwind" => "$comments"}, 
   { "$group"  => { "_id" => "$_id", "cal_date" => { "$max" => "$comments.cal_date" } } },
   { "$project"=> {
                    "outDuration" => { 
                       "$divide" => [ 
                            {"$subtract" => [ Time.now, "$cal_date" ] }, 
                            24*60*60*1000
                       ]
                    }
                  }
   },
   { "$group"  => {
          "_id"    => 1,
          "avgOut" => {"$avg"=>"$outDuration"}
     }
   }  
])

有关更多示例和说明,请参阅https://github.com/mongodb/mongo-ruby-driver/wiki/Aggregation-Framework-Examples

如果您想在 $group 阶段保留其他字段,您可以通过更改管道步骤来添加更多字段,如下所示:

    { $group  : { _id : "$_id", 
                  barcode  : { $first : "$barcode" },
                  cal_date : { $max : "$comments.cal_date" } 
                } 
    } 

如果您不需要原始的_id,您可以在第一行(即_id: "$barcode")中使用“$barcode”而不是“$_id”,但由于您可能要保留多个字段, $first 技巧适用于您想要保留的任意数量。

【讨论】:

  • 如果您想了解更多关于使用日期进行聚合的信息,请查看我博客上的几篇文章:kamsky.org/stupid-tricks-with-mongodb.html
  • 我正在努力解决这个问题,Asya。你能尝试更紧密地使用我的结构吗?也许这会帮助我更好地调试。这种语法对我来说仍然很陌生。
  • 您提供了某种图书馆书籍示例 - 您是否真的可以提供您正在使用的示例文档?您在语法的哪一部分遇到问题?
  • 这就是我正在处理的文档。直接在 rails 中使用您的示例有点棘手,因为您的示例是 shell 输入 javascript,我认为我需要在我的控制器中完成这一切。
  • 你看过这个页面吗? github.com/ntang/mongo-ruby-driver-wiki/blob/master/… - 它有一些关于如何在 Ruby 中构建聚合框架查询的示例 - 我认为您没有明确说明您遇到问题的部分 - 请参阅我对原始问题的评论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-26
  • 1970-01-01
  • 2020-12-28
  • 2016-03-20
相关资源
最近更新 更多