使用PostgreSQL查询生成包含每日统计信息的时间序列

时间:2017-08-25 22:27:46

标签: sql postgresql time-series

我发现自己处于必须制定(对我来说)相当复杂的SQL查询的位置,而我似乎无法理解它。

我有一个名为orders的表和一个相关表order_state_history,可以记录这些订单的状态(见下文)。

我现在需要生成一系列行 - 每天一行 - 包含当天结束时特定州的订单数量(请参阅report)。另外,我只想考虑order.type = 1的订单。

数据驻留在PostgreSQL数据库中。我已经找到了如何使用GENERATE_SERIES(DATE '2001-01-01', CURRENT_DATE, '1 DAY'::INTERVAL) days生成时间序列,这使我可以生成没有记录状态更改的天数。

我目前的方法是将ordersorder_state_history和生成的days系列一起加入,并尝试过滤掉DATE(order_state_history.timestamp) > DATE(days)的所有行,然后以某种方式通过first_value(order_state_history.new_state) OVER (PARTITION_BY(orders.id) ORDER BY order_state_history.timestamp DESC)得到当天每个订单的最终状态,但这是我的一点点SQL经验让我放弃的地方。

我无法解决问题。

这甚至可以在一个查询中解决,还是我会更好地建议通过每天执行一次查询的某种智能脚本来计算数据? 对这个问题有什么合理的解决方法?

orders===            
id       type        
10000    1        
10001    1        
10002    2        
10003    2        
10004    1        


order_state_history===            
order_id    index    timestamp           new_state
10000       1        01.01.2001 12:00    NEW
10000       2        02.01.2001 13:00    ACTIVE
10000       3        03.01.2001 14:00    DONE
10001       1        02.01.2001 13:00    NEW
10002       1        03.01.2001 14:00    NEW
10002       2        05.01.2001 10:00    ACTIVE
10002       3        05.01.2001 14:00    DONE
10003       1        07.01.2001 04:00    NEW
10004       1        05.01.2001 14:00    NEW
10004       2        10.01.2001 17:30    DONE


Expected result===            
date          new_orders    active_orders    done_orders
01.01.2001    1             0                0
02.01.2001    1             1                0
03.01.2001    1             0                1
04.01.2001    1             0                1
05.01.2001    2             0                1
06.01.2001    2             0                1
07.01.2001    2             0                1
08.01.2001    2             0                1
09.01.2001    2             0                1
10.01.2001    1             0                2

1 个答案:

答案 0 :(得分:1)

步骤1.使用值NEW = 1,ACTIVE = 1,DONE = 2:

计算每个订单的累计状态总和
select 
    order_id, timestamp::date as day, 
    sum(case new_state when 'DONE' then 2 else 1 end) over w as state
from order_state_history h
join orders o on o.id = h.order_id
where o.type = 1
window w as (partition by order_id order by timestamp)

 order_id |    day     | state 
----------+------------+-------
    10000 | 2001-01-01 |     1
    10000 | 2001-01-02 |     2
    10000 | 2001-01-03 |     4
    10001 | 2001-01-02 |     1
    10004 | 2001-01-05 |     1
    10004 | 2001-01-10 |     3
(6 rows)

步骤2.根据步骤1中的状态计算每个订单的转换矩阵(2表示NEW-> ACTIVE,3表示NEW-> DONE,4表示ACTIVE-> DONE):

select 
    order_id, day, state,
    case when state = 1 then 1 when state = 2 or state = 3 then -1 else 0 end as new,
    case when state = 2 then 1 when state = 4 then -1 else 0 end as active,
    case when state > 2 then 1 else 0 end as done
from (
    select 
        order_id, timestamp::date as day, 
        sum(case new_state when 'DONE' then 2 else 1 end) over w as state
    from order_state_history h
    join orders o on o.id = h.order_id
    where o.type = 1
    window w as (partition by order_id order by timestamp)
    ) s

 order_id |    day     | state | new | active | done 
----------+------------+-------+-----+--------+------
    10000 | 2001-01-01 |     1 |   1 |      0 |    0
    10000 | 2001-01-02 |     2 |  -1 |      1 |    0
    10000 | 2001-01-03 |     4 |   0 |     -1 |    1
    10001 | 2001-01-02 |     1 |   1 |      0 |    0
    10004 | 2001-01-05 |     1 |   1 |      0 |    0
    10004 | 2001-01-10 |     3 |  -1 |      0 |    1
(6 rows)

步骤3.计算一系列天的每个州的累积总和:

select distinct
    day::date,
    sum(new) over w as new,
    sum(active) over w as active,
    sum(done) over w as done
from generate_series('2001-01-01'::date, '2001-01-10', '1d'::interval) day
left join (
    select 
        order_id, day, state,
        case when state = 1 then 1 when state = 2 or state = 3 then -1 else 0 end as new,
        case when state = 2 then 1 when state = 4 then -1 else 0 end as active,
        case when state > 2 then 1 else 0 end as done
    from (
        select 
            order_id, timestamp::date as day, 
            sum(case new_state when 'DONE' then 2 else 1 end) over w as state
        from order_state_history h
        join orders o on o.id = h.order_id
        where o.type = 1
        window w as (partition by order_id order by timestamp)
        ) s
    ) s
using(day)
window w as (order by day)
order by 1

    day     | new | active | done 
------------+-----+--------+------
 2001-01-01 |   1 |      0 |    0
 2001-01-02 |   1 |      1 |    0
 2001-01-03 |   1 |      0 |    1
 2001-01-04 |   1 |      0 |    1
 2001-01-05 |   2 |      0 |    1
 2001-01-06 |   2 |      0 |    1
 2001-01-07 |   2 |      0 |    1
 2001-01-08 |   2 |      0 |    1
 2001-01-09 |   2 |      0 |    1
 2001-01-10 |   1 |      0 |    2
(10 rows)