在 Rails / Postgresql 上的 Ruby 中查找多个连续日期(日期时间)

Finding multiple consecutive dates (datetime) in Ruby on Rails / Postgresql

我们如何找到 X 连续 个符合条件的日期(由 hour 使用)?

编辑:这里是 SQL fiddle http://sqlfiddle.com/#!17/44928/1

示例:

查找 3 个连续日期 where aa < 2bb < 6cc < 7

鉴于此 table 称为 weather:

timestamp aa bb cc
01/01/2000 00:00 1 5 5
01/01/2000 01:00 5 5 5
01/01/2000 02:00 1 5 5
01/01/2000 03:00 1 5 5
01/01/2000 04:00 1 5 5
01/01/2000 05:00 1 5 5

答案应该 return 来自 02:00, 03:00, 04:00 的 3 条记录。

我们如何在 Rails 上的 Ruby 中执行此操作 - 或者直接在 SQL 中执行此操作(如果那样更好)?

我开始研究基于这个答案的方法: Detect consecutive dates ranges using SQL

def consecutive_dates
  the_query = "WITH t AS (
    SELECT timestamp d,ROW_NUMBER() OVER(ORDER BY timestamp) i
    FROM @d
    GROUP BY timestamp
  )
  SELECT MIN(d),MAX(d)
  FROM t
  GROUP BY DATEDIFF(hour,i,d)"

  ActiveRecord::Base.connection.execute(the_query)
end

但我无法让它工作。

)这是一个缺口和孤岛问题。 Islands 是符合条件的相邻记录,您希望 islands 至少有 3 个记录长。

这是一种使用 window 计数的方法,该计数在每次满足不匹配条件的值时递增以定义组。然后我们可以计算每个组中有多少行,并使用该信息进行过滤。

select  *
from (
    select t.*, count(*) over(partition by a, grp) cnt
    from (
        select t.*,
            count(*) filter(where b <= 4) over(partition by a order by timestamp) grp
        from mytable t
    ) t
) t
where cnt >= 3

假设您每小时有一行,那么获得发生这种情况的 第一个 小时的简单方法是使用 lead():

select t.*
from (select t.*,
             lead(timestamp, 2) over (order by timestamp) as timestamp_2
      from t
      where aa < 2 and bb < 6 and cc < 7
     ) t
where timetamp_2 = timestamp + interval '2 hour';

这会根据条件进行过滤并查看前面两行的行。如果提前两个小时,则连续三行符合条件。

注意:以上将 return 2020-01-01 02:00 和 2020-01-01 03:00,但您似乎只想要最早的。要处理这个问题,请同时使用 lag()

select t.*
from (select t.*,
             lag(timestamp) over (order by timestamp) as prev_timestamp
             lead(timestamp, 2) over (order by timestamp) as timestamp_2
      from t
      where aa < 2 and bb < 6 and cc < 7
     ) t
where timetamp_2 = timestamp + interval '2 hour' and
      (prev_timestamp is null or prev_timestamp < timestamp - interval '1' hour);

如果您确实需要原始行,您可以使用 generate_series() 生成额外的小时数:

select t.timestamp + n.n * interval '1 hour', aa, bb, cc
from (select t.*,
             lead(timestamp, 2) over (order by timestamp) as timestamp_2
      from t
      where aa < 2 and bb < 6 and cc < 7
     ) t cross join lateral
     generate_series(0, 2) n
where timetamp_2 = timestamp + interval '2 hour';

根据问题,您的数据似乎具有精确的时间戳,因此时间戳等式将起作用。如果真实数据更模糊,则可以调整查询以考虑到这一点。