外部联接时间戳范围比较(间隙填充时间序列数据)上的性能不佳

时间:2018-07-06 20:11:50

标签: sql postgresql

我有一些时间序列数据(当前有150万行)。我使用generate_series方法填补了查询中的一些时间空白。

想象一下下面的数据,它们之间的间隔在10 AM和1 PM之间。...

+-------+----------+-------+
| time  | category | value |
+-------+----------+-------+
| 8  AM |        1 |   100 |
| 9  AM |        1 |   200 |
| 10 AM |        1 |   300 |
| 1  PM |        1 |   100 |
| 2  PM |        1 |   500 |
+-------+----------+-------+

我需要我的查询结果来填补该序列的最后一个已知值的任何空白。例如以下...

+-------+----------+-------+
| time  | category | value |
+-------+----------+-------+
| 8  AM |        1 |   100 |
| 9  AM |        1 |   200 |
| 10 AM |        1 |   300 |
| 11 AM |        1 |   300 | (Gap filled with last known value)
| 12 PM |        1 |   300 | (Gap filled with last known value)
| 1  PM |        1 |   100 |
| 2  PM |        1 |   500 |
+-------+----------+-------+

我有一个查询可以执行此操作,但是它确实很慢(在下面的简化示例中约为5秒)。我希望有人可以向我展示更好/更快的方法? 就我而言,我的数据是按分钟计算的。因此,我以1分钟为增量来填补空白。我使用超前/窗口函数来确定每行的NEXT时间戳,因此我知道哪些生成的间隙填充器将使用该值。

请参见下面的示例。...

生成测试数据 (创建一年中每分钟的数据,两个小时前间隔1小时)

create table mydata as
with a as
(
    select 
        date_time
    from 
        generate_series(date_trunc('minute', now())::timestamp - '1 year':: interval, date_trunc('minute', now()::timestamp - '2 hours'::interval), interval '1 minute') as date_time 
    union
    select 
        date_time
    from 
        generate_series(date_trunc('minute', now())::timestamp - '1 hour':: interval, date_trunc('minute', now()::timestamp ), interval '1 minute') as date_time 
),
b as
(
    select category from generate_series(1,10,1) as category
)
select
    a.*,
    b.*,
    round(random() * 100)::integer as value
from
    a 
cross join 
    b
;

create index myindex1 on mydata (category, date_time);
create index myindex2 on mydata (date_time);

查询数据以获取最近5天的所有category = 5数据(填补空白)

with a as
(
    select 
        mydata.*,
        lead(mydata.date_time) over (PARTITION BY category ORDER BY date_time asc) as next_date_time
    from 
        mydata
    where 
        category = 5 
    and
        date_time between now() - '5 days'::interval and now()
),
b as
(
     SELECT generated_time::timestamp without time zone FROM generate_series(date_trunc('minute', now()) - '5 days'::interval, date_trunc('minute', now()), interval '1 minute') as generated_time
)
select
    b.generated_time as date_time,
    a.category,
    a.value
from
    b
left join
    a
on
    b.generated_time >= a.date_time and b.generated_time < a.next_date_time
order by
    b.generated_time desc
;   

此查询功能完善。样本结果...

+---------------------+----------+-------+
|   date_time         | category | value |
+---------------------+----------+-------+
| 2018-07-06 12:17:00 | 5        | 13    |
| 2018-07-06 12:16:00 | 5        | 17    | (gap filled)
| 2018-07-06 12:15:00 | 5        | 17    | (gap filled)
| ...                 | ...      | ...   | (gap filled)
| 2018-07-06 11:18:00 | 5        | 17    | (gap filled)
| 2018-07-06 11:17:00 | 5        | 17    |
| 2018-07-06 11:16:00 | 5        | 62    |
+---------------------+----------+-------+

但是,这部分会降低性能...

b.generated_time >= a.date_time and b.generated_time < a.next_date_time

如果我只是做类似的事情。

b.generated_time = a.next_date_time

然后它很快,但是结果当然不正确。确实不喜欢我做“ and”(或)大于或小于。我认为这可能是因为我正在与即时生成且未编制索引的next_date_time进行比较。但是我什至尝试首先将数据具体化为带有索引的表,性能大致相同。 我将timescaledb扩展标签添加到了这篇文章中,以防它们具有一些内置功能来对此提供帮助。

“解释”结果

Sort  (cost=268537.46..270431.35 rows=757556 width=16)
  Sort Key: b.generated_time DESC
  CTE a
    ->  WindowAgg  (cost=0.44..11057.66 rows=6818 width=24)
          ->  Index Scan using myindex1 on mydata  (cost=0.44..10938.35 rows=6818 width=16)
                Index Cond: ((category = 5) AND (date_time >= (now() - '5 days'::interval)) AND (date_time <= now()))
  CTE b
    ->  Function Scan on generate_series generated_time  (cost=0.02..12.52 rows=1000 width=8)
  ->  Nested Loop Left Join  (cost=0.00..170538.18 rows=757556 width=16)
        Join Filter: ((b.generated_time >= a.date_time) AND (b.generated_time < a.next_date_time))
        ->  CTE Scan on b  (cost=0.00..20.00 rows=1000 width=8)
        ->  CTE Scan on a  (cost=0.00..136.36 rows=6818 width=24)

我正在使用Postgres 10.4。关于如何使其更快的任何建议? 谢谢!

1 个答案:

答案 0 :(得分:0)

因此,我将“部分”回答自己的问题。我确实找到了一种方法来完成我希望做的更好的事情(亚秒级)。但是,它不是那么直观/可读,并且真的很想知道如何使我的第一种方法更快。只是为了了解知识,我真的很想知道自己在做错什么。

无论如何,以下方法似乎有效。我计算每行之间的分钟数,然后只生成一系列具有相同数据但每分钟增加1分钟的行。

我给这几天。如果没有人想出第一种方法的解决方法(或更好的方法),那么我会将其标记为可接受的答案。

select 
    generate_series(date_time, date_time + (((EXTRACT(EPOCH FROM (lead(mydata.date_time) over w - date_time)) / 60)-1) || 'minutes')::interval, interval '1 minute') as date_time,
    category,
    value
from 
    mydata
where 
    category = 5 
and
    date_time between now() - '5 days'::interval and now()
window w as (PARTITION BY category ORDER BY date_time asc) 
order by
    mydata.date_time desc