在 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 < 2
和 bb < 6
和 cc < 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';
根据问题,您的数据似乎具有精确的时间戳,因此时间戳等式将起作用。如果真实数据更模糊,则可以调整查询以考虑到这一点。
我们如何找到 X
连续 个符合条件的日期(由 hour
使用)?
编辑:这里是 SQL fiddle http://sqlfiddle.com/#!17/44928/1
示例:
查找 3 个连续日期 where aa < 2
和 bb < 6
和 cc < 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';
根据问题,您的数据似乎具有精确的时间戳,因此时间戳等式将起作用。如果真实数据更模糊,则可以调整查询以考虑到这一点。