我想在午夜之后的时间之间应用窗口函数。
以下是一只股票每分钟价值的示例数据的一部分。
实际上它确实比这个例子存在的时间更长。
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
以下查询产生原始问题中描述的结果:
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。
我无法测试它,但这应该有效:
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'
.