自行车警告本文可能是自行车构造的球形示例。 如果您知道该问题的标准或更完善的解决方案,那么我将很高兴在评论中看到它。
在完成其中一个项目后,我们需要在该月末草拟有关该期间财务交易的报告,并附上一组小计。
任务通常很简单,在较大的时间间隔内确定所需的时间段,将每个操作附加到适当的时间段,分组并累加数量。
为了生成间隔内的周期,我习惯了使用generate_series函数,该函数通常用于生成数字序列。 我检查了有关生成日期序列的可能性的文档,以示例为例,编写了查询并感到困惑。
select gs::date from generate_series('2018-01-31', '2018-05-31', interval '1 month') as gs;
s |
---|
2018年1月1日 |
2018/02/28 |
2018/03/28 |
2018年04月28日 |
2018/05/28 |
结果是出乎逻辑的意外。 老实说,generate_series函数基于顺序地将移位添加到先前值的原理,诚实地迭代生成日期序列。 同时,在每个步骤中,都要检查接收日期的正确性和编辑。 2月31日没有发生,因此日期更改为2月28日,并且该月的进一步增加使整个序列下降到28日。
UPD 提问后的解释。 通常,最初的任务范围更广-对每月的任意几天的数据进行分组。 例如,按每个月的20日,15日进行分组,但是生成时此类日期没有问题。 我们正在寻找的机制应该同样好地建立每个月10个数字,21个数字的序列,并正确计算月末。
我不知道加法操作一次会出现几个月的情况? 如果我们不是以迭代方式而是以“成批”方式添加间隔,将会发生什么?
select '2018-01-31'::date +interval '1 mons' 28.02.2018 select '2018-01-31'::date +interval '2 mons' 31.03.2018
在这种情况下,诚实地添加。
如何使用这种方法生成必要的日期?
如果知道月数,则非常简单:
select '2018-01-31'::date +make_interval(0, i) as gs from generate_series(0, 4, 1) as i
s |
---|
2018年1月1日 |
2018/02/28 |
2018/03/31 |
2018年4月30日 |
2018/05/31 |
如果只知道开始日期和结束日期怎么办?
通过编写存储的函数和其中的简单循环,可以很简单地解决此问题,但是,当没有可能或希望用不必要的对象阻塞数据库结构时,我们对实现选项很感兴趣。
让我们尝试将任务简化为上一个任务。
下面的代码在某种程度上只是一块面包板,并不装作精美;我们在公司中编写了第一个查询选项,着重于块的灵活性和可互换性
with dates as ( select '2018-01-31'::date as dt1, '2018-05-31'::date as dt2 ), g_age as ( select age( (select dt2 from dates), (select dt1 from dates)) ), months as ( select (extract(year from (select * from g_age))*12 + extract(month from (select * from g_age))+1)::integer ), seq as( select ((select dt1 from dates) + make_interval(0, gs)) as gs from generate_series ( 0, (select * from months), 1 ) as gs where ((select dt1 from dates) + make_interval(0, gs)) <= (select dt2 from dates) ) select * from seq
s |
---|
2018年1月1日 |
2018/02/28 |
2018/03/31 |
2018年4月30日 |
2018/05/31 |
事实证明,该解决方案相当麻烦,但是可以正常工作,并且通过with机制将其集成到其他请求中非常简单。
我们已经执行了该报告,但是这个请求不仅麻烦,而且在整个月中仅一步一步地限制了它的使用,这一想法并没有解决。
选项2
过了一会儿,我意识到顺序日期生成本质上是一个递归过程。 不仅不是纯粹的形式,因为在我们的案例中,根据前一个日期计算下一个日期会导致原始问题。 但是在每一步,我们都可以增加添加到周期开始的时间间隔:
with recursive dates as ( select '2018-01-31'::timestamp as dt1, '2018-05-31'::timestamp as dt2, interval '1 month' as interval ), pr AS( select 1 as i, (select dt1 from dates) as dt union select i+1 as i, ( (select dt1 from dates) + ( select interval from dates)*i)::timestamp as dt from pr where ( ((select dt1 from dates) + (select interval from dates)*i)::timestamp) <=(select dt2 from dates) ) select dt as gs from pr;
s |
---|
2018年1月1日 |
2018/02/28 |
2018/03/31 |
2018年4月30日 |
2018/05/31 |
该查询可以在任何输入时间段和时间间隔内正常工作。