【问题标题】:Find last record of each day查找每天的最后一条记录
【发布时间】:2021-12-05 18:19:01
【问题描述】:

我存储了关于我的耗电量的数据,每分钟都有一个新记录,这里是一个例子:

{"date":1393156826114,"id":"5309d4cae4b0fbd904cc00e1","adco":"O","hchc":7267599,"hchp":10805900,"hhphc":"g","ptec":"c","iinst":13,"papp":3010,"imax":58,"optarif":"s","isousc":60,"motdetat":"Á"}

这样我每天就有大约 1440 条记录。

如何获取每天的最后一条记录?


注意:我在spring java中使用mongodb,所以需要这样的查询:

获取所有度量的示例:

@Query("{ 'date' : { $gt : ?0 }}")
public List<Mesure> findByDateGreaterThan(Date date, Sort sort);

【问题讨论】:

  • 您是否考虑过获取表示日期的范围,然后对最高日期值进行排序和限制?

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

比原来的答案更现代一点:

db.collection.aggregate([
  { "$sort": { "date": 1 } },
  { "$group": {
    "_id": {
      "$subtract": ["$date",{"$mod": ["$date",86400000]}]
    },
    "doc": { "$last": "$$ROOT" }
  }},
  { "$replaceRoot": { "newDocument": "$doc" } }
])

同样的原则适用于您基本上 $sort 集合,然后在所需的分组键上 $group 从分组边界拾取 $last 数据。

让事情变得更清楚一点,因为原始写作是您可以使用$$ROOT 而不是指定每个文档属性,当然$replaceRoot 阶段允许您将该数据完全恢复为原始文档形式。

但一般解决方案仍然是首先$sort,然后在所需的公共键上使用$group,并根据所需属性的分组边界出现的排序顺序保留$last$first .

对于 BSON 日期,而不是问题中的时间戳值,请参阅Group result by 15 minutes time interval in MongoDb,了解有关如何在实际使用和返回 BSON 日期值的不同时间间隔内累积的不同方法。


不太清楚你在这里做什么,但如果我的理解是正确的,你可以这样做。所以要获取每天的最后一条记录:

db.collection.aggregate([
    // Sort in date order  as ascending
    {"$sort": { "date": 1 } },

    // Date math converts to whole day
    {"$project": {
        "adco": 1,
        "hchc": 1,
        "hchp": 1,
        "hhphc": 1,
        "ptec": 1,
        "iinst": 1,
        "papp": 1,
        "imax": 1,
        "optarif": 1,
        "isousc": 1,
        "motdetat": 1,
        "date": 1,
        "wholeDay": {"$subtract": ["$date",{"$mod": ["$date",86400000]}]} 
    }},

    // Group on wholeDay ( _id insertion is monotonic )
    {"$group": 
        "_id": "$wholeDay",
        "docId": {"$last": "$_id" },
        "adco": {"$last": "$adco" },
        "hchc": {"$last": "$hchc" },
        "hchp": {"$last": "$hchp" },
        "hhphc": {"$last": "$hhphc" },
        "ptec": {"$last": "$ptec" },
        "iinst": {"$last": "$iinst" },
        "papp": {"$last": "$papp" },
        "imax": {"$last": "$imax" },
        "optarif": {"$last": "$optarif",
        "isousc": {"$last": "$isouc" },
        "motdetat": {"$last": "$motdetat" },
        "date": {"$last": "$date" },
    }}
])

所以这里的原则是,给定时间戳值,进行日期数学运算以将其投影为每天开始的午夜时间。然后由于文档上的_id 键已经是单调的(一直在增加),那么只需对wholeDay 值进行分组,同时将$last 文档从分组边界中拉出。

如果您不需要所有字段,则只需对您想要的字段进行投影和分组。

是的,您可以在 spring 数据框架中执行此操作。我确定那里有一个包装的命令。但除此之外,获取本机命令的咒语是这样的:

mongoOps.getCollection("yourCollection").aggregate( ... )

作为记录,如果您实际上有 BSON 日期类型而不是时间戳作为数字,那么您可以跳过日期数学:

db.collection.aggregate([
    { "$group": { 
        "_id": { 
            "year": { "$year": "$date" },
            "month": { "$month": "$date" },
            "day": { "$dayOfMonth": "$date" }
        },
        "hchp": { "$last": "$hchp" }
    }}
])

【讨论】:

    【解决方案2】:

    还可以将组键中的时间戳格式化为%Y-%m-%d(例如2021-12-05)和dateToString

    // { timestamp: 1638697946000, value: "a" } <= 2021-12-05 9:52:26
    // { timestamp: 1638686311000, value: "b" } <= 2021-12-05 6:38:31
    // { timestamp: 1638859111000, value: "c" } <= 2021-12-07 6:38:31
    db.collection.aggregate([
    
      { $sort: { timestamp: 1 } },
      // { timestamp: 1638686311000, value: "b" }
      // { timestamp: 1638697946000, value: "a" }
      // { timestamp: 1638859111000, value: "c" }
    
      { $group: {
        _id: { $dateToString: { date: { $toDate: "$timestamp" }, format: "%Y-%m-%d" } },
        last: { $last: "$$ROOT" }
      }},
      // { _id: "2021-12-07", last: { timestamp: 1638859111000, value: "c" } }
      // { _id: "2021-12-05", last: { timestamp: 1638697946000, value: "a" } }
    
      { $replaceWith: "$last" }
    ])
    // { timestamp: 1638697946000, value: "a" } <= 2021-12-05 9:52:26
    // { timestamp: 1638859111000, value: "c" } <= 2021-12-07 6:38:31
    

    这个:

    • 第一个$sorts 文档按timestamps 的时间顺序排列,以便我们稍后可以根据它们的顺序选择最新的文档。

    • 然后$groups 文档按其%Y-%m-%d 格式的时间戳:

      • 首先将时间戳转换为日期时间:{ $toDate: "$timestamp" }
      • 然后将关联的日期时间转换为仅表示年、月和日的字符串:{ $dateToString: { date: ..., format: "%Y-%m-%d" } }
      • 这样对于每个组(即日期),我们可以选择$last(即按时间排序后最新的)匹配文档
        • 选择的是整个文档,由$$ROOT 表示
    • 最终使用$replaceWith 阶段($replaceRoot 的别名)清理组结果。

    【讨论】:

      最近更新 更多