给定一个名为budgets的表,其中包含ratetime_span,我希望为每一天生成每月的每日汇率.time_span必须有开始日期,但可以是开放式的["2023-06-02 00:00:00+00",).

例如,给定以下数据集:

  • rate:$10
  • time_span:["2023-07-19 00:00:00+00",).

假设当前日期为2023-09-05 00:00:00+00,我预计查询结果将显示:

  • $10中的2023-07-19 -> 2023-08-18 a rate应该分成30份,每天的房费是$0.333
  • $102023-08-19 -> 2023-09-05 a rate应该分成17天,每天$0.588英镑.

Attempt 1:

我首先try 通过从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. 在第二个横向函数调用i中,生成与mon_days中的天数一样多的行--实际上就是CROSS JOIN LATERAL的简写.请参见:

  3. 计算显示日期the_day及其在外部SELECT中的daily_rate.

这种计算有很多细节:

为了避免时区与当地时间和夏令时之间的不确定情况和模棱两可,我构建了time_span的数据类型daterange,而不是您的示例数据所建议的tstzrange.

正如您的样本数据所显示的那样,我将每个月的实际开始日期设为timespan,而不是每月的第一天.

我假设您想用当前日期结束时间序列,所以我使用LEAST而不是COALESCE.(出于不同的原因,又增加了LEAST个.)

我将利率除以每个月的实际天数.

rate应为数字类型,以避免舍入误差,并且开箱即可使用round().

我假设所有的第NOT NULL列.

请注意,date - date → intdate + 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相关问答推荐

Microsoft Access UNION将长文本字段限制为255个字符

具有2个共享列的两个表的Amazon RSQL合并

无效和不匹配的计数

Stack Exchange站点上的最短帖子(按正文长度计算,用户名为原始发帖(SEDE))

对非RUST源代码字符串使用`stringify!`,例如SQL查询

连接三个表的正确方式是什么?在这三个表中,可以显示在一个表上的行将在其他表中显示结果

PostgreSQL使用SQL子查询在时间间隔内 Select 数据

使用generate_series()时,LEFT联接缺少日期/间隔

如何在多列上编写具有不同条件的查询?

将SQL Server查询改进为;线程安全;

按二维数组的第一个元素排序

我需要在 ASP.NET C# 中获取 2 个 SQL 查询结果的平均值

确定小数中使用的精度位数

使用ALTER TABLE无法删除列

Oracle函数中无法动态迭代创建的SYS_REFCURSOR

根据是否存在值组合分组并 Select 行

如何 Select 一列具有最小值而另一列具有给定值的记录?

使用标准SQL 触发更新当前日期

SQL - 使用子查询返回多行的 LIKE 命令

SQL 计数和过滤查询优化