How to use a Postgresql window function to filter between times that beyond midnight?

问题描述 投票:0回答:2

我想在午夜之后的时间之间应用窗口函数。
以下是一只股票每分钟价值的示例数据的一部分。
实际上它确实比这个例子存在的时间更长。

datetime的数据类型为timestamptz,其他为int。

我想查询每天22:30-04:21之间首价和尾价的差异

我该怎么做?

我想做的似乎是跟随但它不起作用。

select distinct datetime::date, first_value(open) over w as first, last_value(close) over w as last 
from price_table window w as 
(partition by datetime::time range between 22:30 and 04:21); 

价格表:

datetime    open    high    low     close   volume // type of datetime is timestamptz others are int.
...
 
// there is a more older value (several years actually) 

2023/01/03  23:50   25830   25840   25830   25830   1139
2023/01/03  23:51   25835   25835   25820   25825   786
2023/01/03  23:52   25825   25845   25825   25835   1196
2023/01/03  23:53   25840   25840   25825   25825   874
2023/01/03  23:54   25825   25835   25820   25825   680
2023/01/03  23:55   25820   25825   25810   25815   1237
2023/01/03  23:56   25810   25815   25805   25810   1163
2023/01/03  23:57   25810   25835   25810   25815   1753
2023/01/03  23:58   25810   25840   25810   25830   823
2023/01/03  23:59   25835   25845   25830   25845   1008
2023/01/03  0:00    25845   25855   25840   25850   1235
2023/01/03  0:01    25850   25850   25845   25845   439
2023/01/03  0:02    25845   25850   25835   25840   1146
2023/01/03  0:03    25840   25855   25840   25845   668
...

预期结果表:(

diff
是期间最后一个值(收盘价)和期间第一个值(收盘价)相减的结果。)

date          open     close    diff
2023/01/03    25810    25840    30
2023/01/04    25830    25820    20
postgresql datetime window-functions
2个回答
0
投票

以下查询产生原始问题中描述的结果:

WITH daily_prices AS (
  SELECT
      DATE_TRUNC('day', datetime + INTERVAL 'PT1H30M')::date AS date,
      (ARRAY_AGG(open ORDER BY datetime))[1] AS open,
      (ARRAY_AGG(close ORDER BY datetime DESC))[1] AS close
    FROM
      price_table
    WHERE
         datetime::time >= '21:30'
      OR datetime::time <= '04:21'
    GROUP BY
      date)
SELECT date, open, close, close - open as diff
  FROM daily_prices
  ORDER BY date;

查询使用公用表表达式(CTE)来减少冗余计算。虽然在这种情况下不是主要问题,但当计算更复杂或产生多个地方需要的值时,重复代码可能会成为维护问题。即使像这样简单的查询,重复计算也会使代码更难理解,增加引入缺陷的机会。

查询首先通过添加偏移量确定每个价格记录的生效日期,然后保留结果值的日期部分。行按未调整的时间过滤,并按 price_date 分组,而不管记录的时间是在午夜之前还是之后。

由于

FIRST_VALUE
LAST_VALUE
是窗口函数而不是聚合函数,它们不能引用非分组值;但是,可以通过将值收集到排序数组中并取第一个元素来获得所需的结果。为了获得最后一个值,数组按降序排序,因此不必知道数组中有多少元素。

使用

FIRST_VALUE
LAST_VALUE
的直觉是合理的,但会导致稍微冗长的解决方案。将上面的查询与以下查询进行对比:

WITH parms AS (
  SELECT INTERVAL 'PT1H30M' as time_shift),
daily_prices AS (
  SELECT DISTINCT
    (datetime + parms.time_shift)::date AS date,
    FIRST_VALUE(open) OVER (PARTITION by (datetime + parms.time_shift)::date
                            ORDER BY datetime) AS open,
    LAST_VALUE(close) OVER (PARTITION by (datetime + parms.time_shift)::date
                             ORDER BY datetime
                             ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS close
    FROM
      parms CROSS JOIN price_table
    WHERE
         datetime::time >= '22:30'
      OR datetime::time <= '04:21')
SELECT date, open, close, close - open as diff
  FROM daily_prices
  ORDER BY date;

函数名称

FIRST_VALUE
LAST_VALUE
反映了要执行的操作,因此比在第一个查询中使用
ARRAY_AGG
更直观;然而,这种优势被所需的额外代码量所抵消。一些多余的代码可以去掉;例如,如果使用
FIRST_VALUE
代替
LAST_VALUE
并且顺序更改为
datetime DESC
,则可以省略窗框定义。虽然这样做减少了代码量,但它也省去了
LAST_VALUE
相对于
ARRAY_AGG
的直观优势。一个更大的问题是需要重复分区表达式。在如此简短的查询中,这只是一个小麻烦,但随着重复次数的增加,它会成为一个更重要的问题。

避免使用 SQL 关键字作为列名。

CLOSE
DATE
OPEN
是PostgreSQL和SQL中的关键字,在SQL中也是保留的。我也会避免使用 datetime 作为列名,它不是描述性的,在某些 SQL 实现中用作类型或函数名;例如,SQLite、MySQL 和 SQL Server。


-1
投票

我无法测试它,但这应该有效:

select distinct (datetime - INTERVAL '5 hour')::date,
first_value(open) over (partition by (datetime - INTERVAL '5 hour')::date order by datetime::time) as open,
last_value(close) over (partition by (datetime - INTERVAL '5 hour')::date order by datetime::time) as close,
first_value(open) over (partition by (datetime - INTERVAL '5 hour')::date order by datetime::time) - last_value(close) over (partition by (datetime - INTERVAL '5 hour')::date order by datetime::time) as diff
from price_table
where datetime::time between '22:30' and '04:21'; 

这里午夜前后的值被认为是午夜前一天的值,例如“2023/01/04 0:03”被认为是 2023/01/03。如果您想在午夜后的第二天考虑它们,则必须将

- INTERVAL '5 hour'
更改为
+ INTERVAL '2 hour'
.

© www.soinside.com 2019 - 2024. All rights reserved.