给定一个名为budgets
的表,其中包含rate
和time_span
,我想生成每天的每月每日费率。 time_span
必须有一个开始日期,但可以是开放式的["2023-06-02 00:00:00+00",)
。
例如,给定以下数据集:
费率
:10 美元time_span
:["2023-07-19 00:00:00+00",)
假设当前日期是 2023-09-05 00:00:00+00
我希望查询结果显示:
2023-07-19 -> 2023-08-18
费率
10 美元
应除以 30 以获得每日费率$0.333
2023-08-19 -> 2023-09-05
费率
10 美元
应分为 17 天,每日费率为$0.588
尝试 1:
我首先尝试通过从 time_span
生成一系列数据,然后将比率除以一个月的 date_part
间隔来解决此问题。这是有缺陷的,因为 date_part
中的 time_bucket
没有考虑其每月窗口内的正确天数。
SELECT
CAST(time_bucket AS TIMESTAMP) AT TIME ZONE 'America/New_York' AS time_bucket,
rate / DATE_PART('days', time_bucket + INTERVAL '1 month - 1 day') AS daily_rate
FROM (
SELECT
DATE(generate_series(LOWER(time_span)::TIMESTAMP, COALESCE(UPPER(time_span),NOW())::TIMESTAMP, '1 day')) AS time_bucket,
rate
FROM budgets
) AS daily_rates;
如何为该预算表生成每月的每日费率?
2023-07-19
-0.333
2023-07-20
-0.333
2023-07-21
-0.333
- ...
2023-08-19
-0.588
2023-08-20
-0.588
- ...
最佳答案
SELECT mon_start::date + i AS the_day, round(rate/mon_days, 3) AS daily_rate
FROM budgets b
CROSS JOIN LATERAL (
SELECT *, LEAST(mon_start + interval '1 month', '2023-09-05')::date - mon_start::date AS mon_days
FROM generate_series (lower(timespan)::timestamp
, LEAST(upper(timespan)::timestamp, '2023-09-05')
, interval '1 month') mon_start
) m
, generate_series (0, m.mon_days - 1) i
WHERE id = 1;
请注意,示例中第一个月的汇率是 0.323 而不是 0.333,因为 8 月有 31 天,而不是 30 天。
在第一个横向子查询中生成一系列月份
m
计算一个月或更短的此费率期间的天数:mon_days
生成与
mon_days
中的天数一样多的行在第二个横向函数调用i
- 实际上只是CROSS JOIN LATERAL
的简写。请参阅:计算显示日期
the_day
及其daily_rate
在外SELECT
.
这种计算有很多细则:
为了避免未定义的极端情况以及时区与本地时间和夏令时的歧义,我基于数据类型 daterange
进行构建。对于 time_span
而不是 tstzrange
正如您的样本数据所示。
正如您的样本数据所示,我将每月的切片基于 timespan
中的实际开始日期。 ,不是本月的第一天。
我假设您想以当前日期结束时间序列,因此我使用 LEAST
而不是COALESCE
。 (还有另一个 LEAST
出于不同的原因。)
我将该费率除以每个月切片中的实际天数。
rate
应输入数字以避免舍入错误并使用 round()
开箱即用。
我假设所有列 NOT NULL
.
请注意 date - date → int
和 date + int → date
.
我根据第一个generate_series()
调用timestamp
,不是date
,因为:
您的实际查询将使用实际的当前日期(或时间戳),而不是给定的“今天”的固定 2023-09-05:
SELECT mon_start::date + i AS the_day, round(rate/mon_days, 3) AS daily_rate, *
FROM budgets b
CROSS JOIN LATERAL (
SELECT *, LEAST(mon_start + interval '1 month', LOCALTIMESTAMP)::date - mon_start::date AS mon_days
FROM generate_series (lower(timespan)::timestamp
, LEAST(upper(timespan)::timestamp, LOCALTIMESTAMP) -- !
, interval '1 month') mon_start
) m
, generate_series (0, m.mon_days - 1) i
WHERE id = $id;
关于sql - 从每月生成的系列中生成每日汇率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77047772/