【问题标题】:How can I select one row of data per hour, from a table of time stamps?如何从时间戳表中每小时选择一行数据?
【发布时间】:2013-05-04 09:07:50
【问题描述】:

如果这令人困惑,请原谅,因为我对 postgresql 不是很熟悉。我有一个 postgres 数据库,其中包含一个充满“站点”的表。每个站点大约每小时报告一次,当它报告时,它会在此表中输入一个条目,如下所示:

site |      tstamp
-----+--------------------
6000 | 2013-05-09 11:53:04
6444 | 2013-05-09 12:58:00
6444 | 2013-05-09 13:01:08
6000 | 2013-05-09 13:01:32
6000 | 2013-05-09 14:05:06
6444 | 2013-05-09 14:06:25
6444 | 2013-05-09 14:59:58
6000 | 2013-05-09 19:00:07

如您所见,时间戳几乎从不贴在鼻子上,有时仅在几分钟/秒内就会有 2 个或更多。此外,某些网站(有时)不会一次报告数小时。我只想每小时每个站点选择一个条目(尽可能接近每个小时)。我怎样才能以有效的方式做到这一点?我还需要将此扩展到其他时间范围(例如每个站点每天一个条目 - 尽可能接近午夜)。

感谢您的任何建议。

【问题讨论】:

    标签: sql database postgresql timestamp


    【解决方案1】:

    你可以使用DISTINCT ON:

    select distinct on (date_trunc('hour', tstamp)) site, tstamp
    from t
    order by date_trunc('hour', tstamp), tstamp
    

    如果您关心获得的条目,请注意 ORDER BY。

    或者,您可以使用row_number window function 标记感兴趣的行,然后从派生表中剥离每个组中的第一个结果:

    select site, tstamp
    from (
        select site, tstamp,
               row_number() over (partition by date_trunc('hour', tstamp) order by tstamp) as r
        from t
    ) as dt
    where r = 1
    

    同样,您需要调整 ORDER BY 以选择每个日期感兴趣的特定行。

    【讨论】:

    • @BLuFeNiX:这个!对于几个小时或其他时间间隔,请使用date_trunc('hour', tstamp)Herehere 是任意间隔的相关答案。
    • @ErwinBrandstetter:我怎么会混淆“小时”和“日期”?奇怪的是,大脑、手指和眼睛有时生活在不同的世界中。
    • @muistooshort:谢谢!您的第一个查询有效,但速度很慢。关于如何使它更快的任何建议?我在第二个查询中遇到 row_number() 错误。另外,这个查询最终会被分页,这有关系吗?
    • @BLuFeNiX:你有哪个版本的 PostgreSQL? date_trunc('hour', stamp) 上的索引可能会有所帮助,但函数值(和窗口函数)的索引取决于 PostgreSQL 版本。
    • @muistooshort:我的客户端是 9.2.4,服务器是 9.2.2,我还需要别的吗?感谢您的帮助。
    【解决方案2】:

    您正在寻找最接近的每小时值。有些是在小时之前,有些是在之后。这使得这是一个棘手的问题。

    首先,我们需要确定在特定小时内有效的值范围。为此,我会考虑从一小时前的 15 分钟到之后的 45 分钟的任何时间作为那个小时。因此,2:00 的考虑时间从 1:45 到 2:45(任意,但对您的数据来说似乎是合理的)。我们可以通过将时间戳移动 15 分钟来做到这一点。

    其次,我们需要得到最接近小时的值。所以,我们更喜欢 1:57 到 2:05。我们可以通过考虑 (57, 60 - 57, 5, 60 - 5) 中的第一个值来做到这一点。

    我们可以将这些规则放入 SQL 语句中,使用row_number()

    select site, tstamp, usedTimestamp
    from (select site, tstamp,
                 date_trunc('hour', tstamp + 'time 00:15') as usedTimestamp
                 row_number() over (partition by site, to_char(tstamp + time '00:15', 'YYYY-MM-DD-HH24'),
                                    order by least(extract(minute from tstamp), 60 - extract(minute from tstamp))
                                   ) as seqnum
          from t
         ) as dt
    where seqnum = 1;
    

    【讨论】:

    • 我在 row_number() 上收到错误。有什么办法可以解决这个问题?此外,这个查询最终会被分页。
    • @BLuFeNiX 。 . .我修复了语法错误。 SQLFiddle 在这里 (sqlfiddle.com/#!12/d41d8/937/0)。
    • 谢谢!我去看看。
    【解决方案3】:

    关于您问题的可扩展性方面。

    I also will need to extend this to other time frames (like one entry per site per day

    根据不同的站点 ID 集,并使用(递归)CTE,我将在指定的 StartDateTime、EndDateTime 范围内构建一个集合,该集合由每个站点每小时(或其他指定的时间间隔)一个条目组成。

              SITE..THE DATE-TIME-HOUR
              6000  12.1.2013 00:00:00
              6000  12.1.2013 01:00:00
              .
              .
              . 
              6000  12.1.2013 24:00:00              
              7000  12.1.2013 00:00:00        
              7000  12.1.2013 01:00:00
              .
              .
              . 
              7000  12.1.2013 24:00:00
    

    然后,我会根据您的 SITES 登录站点 ID 以及 CTE 时间点和 LOG 时间点之间的最小绝对差值离开加入该 CTE。

    这样您就可以确保每个站点每个时间间隔都有一行。

    附:对于长时间未打电话回家的网站,其最近的电话打入时间戳将重复多次,作为最接近的可用时间戳。

    【讨论】:

      猜你喜欢
      • 2014-05-09
      • 1970-01-01
      • 1970-01-01
      • 2020-09-09
      • 2021-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多