sql - 从每月生成的系列中生成每日汇率

标签 sql postgresql

给定一个名为budgets的表,其中包含ratetime_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;

fiddle

请注意,示例中第一个月的汇率是 0.323 而不是 0.333,因为 8 月有 31 天,而不是 30 天。

  1. 在第一个横向子查询中生成一系列月份 m 计算一个月或更短的此费率期间的天数:mon_days

  2. 生成与 mon_days 中的天数一样多的行在第二个横向函数调用 i - 实际上只是 CROSS JOIN LATERAL 的简写。请参阅:

  3. 计算显示日期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/

相关文章:

mysql - 将选中的列表框插入数据库

php - 如何使表格将相同的列打印到一行中?

SQL Server 输出参数

mysql - SQL - 如果另一个表存在则删除行

postgresql - LATERAL JOIN 不使用三元组索引

sql - 从 PostgreSQL 表中提取特定列并对其值进行更新

java - Java 中 Postgresql 日期从时间戳到字符串

php - 连接具有不同列值的行

postgresql - 错误 : column "data_type" of relation does not exist

postgresql - Redshift 连接问题 : FATAL: password authentication failed for user