【问题标题】:Oracle PLSQL Recurrence Pattern RFC 2445Oracle PLSQL 重复模式 RFC 2445
【发布时间】:2020-02-04 07:26:50
【问题描述】:

我有一个必要条件,我需要使用 PLSQL 将 RFC 2445 Recurrence Pattern 转换为 Dates

例子:

RRULE = FREQ=DAILY;INTERVAL=5;COUNT=10

根据该规则,我需要编写一个包含该模式接下来出现的 10 次的表格。如下图所示,考虑开始日期为1/1/2019 12:00:00 AM

Oracle 是否提供任何允许我执行此操作的 PLSQL 包?如果不知道,有人知道这方面的任何 PLSQL 项目倡议吗?

Ps:这与 Oracle 在 Job Schedules 中使用的模式完全相同。

【问题讨论】:

标签: oracle plsql recurrence rfc2445


【解决方案1】:

您可以编写一个 PL/SQL 函数来解析字符串并输出流水线的日期集合:

Oracle 设置

CREATE FUNCTION parseRRule(
  rrule      IN VARCHAR2,
  start_date IN DATE
) RETURN SYS.ODCIDATELIST PIPELINED
IS
  freq    VARCHAR2(10) := UPPER( REGEXP_SUBSTR( rrule, '(^|;)FREQ=(MONTHLY|WEEKLY|DAILY|HOURLY)(;|$)', 1, 1, 'i', 2 ) );
  inter   NUMBER(4,0)  := TO_NUMBER( REGEXP_SUBSTR( rrule, '(^|;)INTERVAL=(\d+)(;|$)', 1, 1, 'i', 2 ) );
  cnt     NUMBER(4,0)  := TO_NUMBER( REGEXP_SUBSTR( rrule, '(^|;)COUNT=(\d+)(;|$)', 1, 1, 'i', 2 ) );
  dt      DATE         := start_date;
  step_ds INTERVAL DAY TO SECOND;
  step_m  NUMBER(3,0);
BEGIN
  IF freq IS NULL OR inter IS NULL OR cnt IS NULL OR dt IS NULL THEN
    RETURN;
  END IF;
  IF freq = 'MONTHLY' THEN
    step_ds := INTERVAL '0' DAY;
    step_m  := inter;

  ELSIF freq = 'WEEKLY' THEN
    step_ds := inter * INTERVAL '7' DAY;
    step_m  := 0;
  ELSIF freq = 'DAILY' THEN
    step_ds := inter * INTERVAL '1' DAY;
    step_m  := 0;
  ELSIF freq = 'HOURLY' THEN
    step_ds := inter * INTERVAL '1' HOUR;
    step_m  := 0;
  ELSE
    NULL;
    -- raise exception
  END IF;
  PIPE ROW ( dt );
  FOR i IN 1 .. cnt - 1 LOOP
    dt := ADD_MONTHS( dt + step_ds, step_m );
    PIPE ROW ( dt );
  END LOOP;
END;
/

查询

SELECT *
FROM   TABLE(
  parseRRule(
    rrule      => 'FREQ=DAILY;INTERVAL=5;COUNT=10',
    start_date => DATE '2019-01-01'
  )
)

输出

| COLUMN_VALUE | | :----------- | | 2019-01-01 | | 2019-01-06 | | 2019-01-11 | | 2019-01-16 | | 2019-01-21 | | 2019-01-26 | | 2019-01-31 | | 2019-02-05 | | 2019-02-10 | | 2019-02-15 |

db小提琴here

【讨论】:

    【解决方案2】:

    DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING可能能够做到这一点。

    该包支持的语法似乎类似于 RFC 2445,但并不完全相同。下面的 PL/SQL 块根据日历字符串打印出日期。有一些复杂性,例如解析出COUNT=10 以确定重复计算的次数。

    declare
        --Test different calendar strings and start dates.
        --p_calendar_string varchar2(4000) := 'FREQ=DAILY;INTERVAL=5;';
        p_calendar_string varchar2(4000) := 'FREQ=DAILY;INTERVAL=5;COUNT=10';
        p_start_date date := timestamp '2019-01-01 00:00:00';
    
        v_next_run_date date;
        v_count number;
        --Find the COUNT and remove it rom the calendar string, if it exists.
        procedure get_and_remove_count(p_calendar_string in out varchar2, p_count out number) is
        begin
            if lower(p_calendar_string) like '%count%' then
                p_count := to_number(regexp_substr(p_calendar_string, 'COUNT=([0-9]+)', 1, 1, null, 1));
                p_calendar_string := regexp_replace(p_calendar_string, 'COUNT=[0-9]+;?');
            else
                p_count := 1;
            end if;
        end;
    begin
        get_and_remove_count(p_calendar_string, v_count);
    
        --TEST
        --dbms_output.put_line('String: '||p_calendar_string||', count: '||v_count);
    
        --Start with the original date.
        v_next_run_date := p_start_date-1/24/60/60;
    
        --Loop through the COUNT and display all dates.
        for i in 1 .. v_count loop
    
            dbms_scheduler.evaluate_calendar_string
            (
                calendar_string   => p_calendar_string,
                start_date        => p_start_date,
                return_date_after => v_next_run_date,
                next_run_date     => v_next_run_date
            );
    
            dbms_output.put_line(to_char(v_next_run_date, 'mm/dd/yyyy hh:mi:ss am'));
        end loop;
    end;
    /
    

    输出:

    01/01/2019 12:00:00 am
    01/06/2019 12:00:00 am
    01/11/2019 12:00:00 am
    01/16/2019 12:00:00 am
    01/21/2019 12:00:00 am
    01/26/2019 12:00:00 am
    01/31/2019 12:00:00 am
    02/05/2019 12:00:00 am
    02/10/2019 12:00:00 am
    02/15/2019 12:00:00 am
    

    【讨论】:

    • “我无法让函数在今天作为第一次迭代返回。” - 只需使用v_next_run_date := p_start_date-1;(或v_next_run_date := localtimestamp-1;)作为初始开始日期。使用v_next_run_date := p_start_date-1/24/60/60; 也将覆盖更小的间隔,例如小时。
    【解决方案3】:

    您可以使用connect by 查询来实现此目的,但您需要找出获取频率和计数的方法(使用regexp)并在以下查询中使用它们:

    Select date '2019-01-01' + (level-1) * 5 as dates
    From dual
    Connect by level <= 10;
    

    干杯!!

    【讨论】:

    • 此解决方案仅适用于一个特定日期。我相信 regisxp 正在为任何日历字符串寻找通用解决方案。
    • 是的,但是如果他们想改用 HOURLY 或 BYDAY 等怎么办。他们必须自定义每个代码。
    猜你喜欢
    • 1970-01-01
    • 2021-06-13
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 2013-04-10
    • 1970-01-01
    • 2015-11-06
    • 2022-11-22
    相关资源
    最近更新 更多