“每月的第三个星期五”到 PLPGSQL 中的时间戳?

'3rd Friday of the Month' to a timestamp in PLPGSQL?

我有一个数据库列,提供有关文件进入频率的信息。

Frequency_month
-------------
3rd Friday of the month
2nd Tuesday of the month
3rd Thursday of the month

我需要更新此列并将其设置为时间戳。例如

Frequency_month
-------------
2020-05-21 00:00:00
2020-05-11 00:00:00
2020-05-20 00:00:00

如何使用 postgres PLPGSQL 语言完成此操作?

以下是您要查找的内容。就解析 Frequency_month 而言,它施加了以下限制:

  • 字符串中的第一个字符是表示亲戚的数字 数.
  • 后面是 2 个字符的序号规范(st、nd 等)和一个 space。 其实任意3个字符,都不检查
  • 位置 5 - 7 表示英文星期几 (dow) 的前 3 个字符。

如果其中任何一个不满足您将需要更改 S1 子查询。 此外,它还要求您提供参考日期。这可以是感兴趣的月份中的任何日期。请参阅@sddk 的评论。

进行如下:

  1. 解析以上内容,提取周数、星期几和最后 前一个月的第几天。 (S1).
  2. 确定指定星期几的 ISODOW id 编号并 上月最后一个月的 DOW。 (S2).
  3. 使用ISODOW id numbers确定,确定第一个 目标月份中目标日期的发生。 (S3).
  4. 将#3 中的日期再调整几周。 (S4).
  5. 最后,如果#4 中的结果日期仍在目标月份 return 日期表 #4。如果不是同一个月,则 return 无效的。这发生在当月没有第 n 个道指或道指 指定不正确。

我已将以上内容包装到一个 SQL 函数中,使参数化变得容易。参见 Demo

create or replace
function frequency_month( frequency_string text  
                         , target_month     date
                         ) 
    returns date
   language sql 
as $$
    with day_names( l_days) as 
         ( values (array['mon','tue','wed','thu','fri','sat','sun']) ) 
    select -- if the calculated date in still in the target month return that date else return null
            -- covers invalid week in frequency 6th Friday or 0th Monday 
            case when extract(month from target_date) = extract (month from  target_month) 
                 then target_date
                 else null
            end
      from ( -- Advance from first dow in month the number of weeks to desirded dates  
             --select (first_of_mon + (7*(rel_num-1)) * interval '1 day')::date target_date
             select (first_of_mon + (rel_num-1) * interval '1 week')::date target_date
                from ( -- with last day of prior month get first DOW week of target month
                       select case when dow_day_nbr <= from_day_nbr 
                                    then (from_date +  (dow_day_nbr-from_day_nbr+7) * interval '1 days' )::date 
                                    else (from_date +  (dow_day_nbr-from_day_nbr) * interval '1 days' )::date 
                               end first_of_mon
                             ,  rel_num
                         from ( -- Pick up ISODOW numbers 
                                select array_position(l_days, (substring(to_char(from_date, 'day'),1,3))) as  from_day_nbr 
                                      , array_position(l_days, lower(substring(rel_dow,1,3)))  as dow_day_nbr
                                      , from_date
                                      ,  rel_num
                                   from day_names 
                                   cross join ( -- get last day of prior month, desired relative day, relative dow  
                                                select substr(frequency_string,1,1)::integer rel_num
                                                      , lower(substr(frequency_string,5,3)) rel_dow
                                                      , (date_trunc('month',target_month)  - interval '1 day')::date from_date
                                               ) s1
                                ) s2
                       ) s3
            ) s4;
$$; 

注意:如果不需要某个功能,该演示还包括独立版本。