企业项目管理、ORK、研发管理与敏捷开发工具平台

网站首页 > 精选文章 正文

SQL打印月份日历

wudianyun 2025-03-03 20:32:39 精选文章 17 ℃

SQL打印月份日历

需求:随意输入一日期,用 SQL 输出该日期的当月的日历

解题分析:

  1. 因为日期是随意输入的,很自然的,我们就想到,需要知道输入日期的当月的第一天,以及最后一天,当前月份的天数。
  2. 因为日历均是按照周来打印的,那么,接下来,获取周数也就必然的步骤。
  3. 看上去,所有的基础数据都通过以上两步获取了,那么进行行列转换,也就可以得到我们想要的结果集。

依据以上思路,实现的完整SQL脚本如下(PG):

with recursive cte_base as (

select date_trunc('month', TIMESTAMP '2025-1-03')::date as daydate

, 1 as day

union

select (daydate + 1)::date

, day +1

from cte_base

where day < ((date_trunc('month',TIMESTAMP '2025-1-03') + interval'1 month - 1 day')::date - (date_trunc('month', TIMESTAMP '2025-1-03'))::date + 1)

),

cte_days as (

select extract(isodow from daydate) as dayofweek

, day

, extract(week from daydate) as week

from cte_base

),

cte_tmp as (

select sum(case when dayofweek = 1 then day else null end ) as mon

, sum(case when dayofweek = 2 then day else null end ) as tue

, sum(case when dayofweek = 3 then day else null end ) as wed

, sum(case when dayofweek = 4 then day else null end ) as thu

, sum(case when dayofweek = 5 then day else null end ) as fri

, sum(case when dayofweek = 6 then day else null end ) as sat

, sum(case when dayofweek = 7 then day else null end ) as sun

, week

from cte_days

group by week

)

select week

, mon

, tue

, wed

, thu

, fri

, sat

, sun

from cte_tmp

order by week;

执行一下,输出结果如下图所示:

从输出结果看上去,的确是我们想要的结果,似乎很完美,那么真的完美了吗?

让我们继续改变日期为2022-01-05来试试看,打印结果如下图所示:

发现了什么?

1与2居然排序到了最后,这是为什么呢?再检查一下,不难发现,Week是52,还是上一年的Week,而并非是当年的1,此时依据Week来做排序,必然得不到我们想要的结果。

同理,如果我们尝试日期2019-12-05,打印结果如下图所示:

也依然不是我们想要的结果。

由此,不难想到一个问题:当月份为12月和1月的时候,可能存在week跨年的情况发生,而导致出现如上的问题。

接下来,我们需要做一点点小的技术处理。

技术处理思路分析:

  1. 既然依据Week进行排序是会导致错误,那么能否构造一个新的排序规则来代替Week排序呢?
  2. 我们观察一下数据,每一Week的日期都是从小到大排序的,能否借助一下这个日期呢?
  3. 因为理论上来讲,每一个Week最少会覆盖一天,那么我就获取每一个Week的一天,让它来和Week做个对应关系,就像初中数学几何解题使用到的辅助线一样。这里我们将Week分组,获取最小的一天,当然最大的一天也是可以的。
  4. 利用每一Week的最小一天,我们再重新排序,获取到的就是正确的排序值了。
  5. 最后,我们利用这个新的排序规则来对前面的结果集进行排序,最终得到我们想要的结果。至此,此题顺利解决。

依据以上思路,实现的最终完整SQL脚本如下(PG):

with recursive cte_base as (

select date_trunc('month', TIMESTAMP '2025-01-03')::date as daydate

, 1 as day

union

select (daydate + 1)::date

, day +1

from cte_base

where day < ((date_trunc('month',TIMESTAMP '2025-01-03') + interval'1 month - 1 day')::date - (date_trunc('month', TIMESTAMP '2025-01-03'))::date + 1)

),

cte_days as (

select extract(isodow from daydate) as dayofweek

, day

, extract(week from daydate) as week

from cte_base

),

cte_tmp as (

select sum(case when dayofweek = 1 then day else null end ) as mon

, sum(case when dayofweek = 2 then day else null end ) as tue

, sum(case when dayofweek = 3 then day else null end ) as wed

, sum(case when dayofweek = 4 then day else null end ) as thu

, sum(case when dayofweek = 5 then day else null end ) as fri

, sum(case when dayofweek = 6 then day else null end ) as sat

, sum(case when dayofweek = 7 then day else null end ) as sun

, week

from cte_days

group by week

order by week

),

cte_min as (

select min(day) as minday

, week

from cte_days

group by week

),

cte_rownumber as (

select row_number() over (order by minday) as rownumber

, week

from cte_min

)

select t.week

, t.mon

, t.tue

, t.wed

, t.thu

, t.fri

, t.sat

, t.sun

from cte_rownumber r

join cte_tmp t

on r.week = t.week

order by r.rownumber;


如果有不妥之处,请大家批评指正。

谢谢

最近发表
标签列表