SQL: 存储和查询 `cron` 字段

SQL: Store and query `cron` field

我正在构建一个 cron-as-a-service,允许用户输入他们的 cron 表达式,并定期做一些事情。

这是我的 table:

的简单版本
create table users (
  id serial not null primary key,
  -- `text` seems the most straightforward data type, any other suggestions?
  cron text not null,
  constraint valid_cron_expression CHECK (cron ~* '{some_regex}'),
  -- snip --
  -- maybe some other fields, like what to do on each cron call
)

我的后端每分钟运行一次 SQL 查询。如何查询 cron 字段匹配当前时间戳(四舍五入到最接近的分钟)的所有行?

编辑:用户将 cron 字段输入为 cron expression,例如5 4 * * *.

编辑 2:更正了 cron 时间分辨率是分钟而不是秒的事实。

这解决了问题的原始版本。

假设 cron 是某种时间戳,您将使用:

where cron >= date_trunc('second', now()) and
      cron < date_trun('second', now()) + interval '1 second'

首先,您不需要每秒查询一次,因为 cron 的分辨率只有一分钟。

接下来,将 cron 调度程序表达式与时间戳进行比较并非易事。 我不知道任何能够解析 cron 表达式的 PostgreSQL 模块。

有两种选择,要么编写自己的函数来进行比较,要么使用所用编程语言中的外部库在数据库外部进行比较。

在这里您将找到一个可以轻松移植到 PostgreSQL 的 Oracle 函数的示例实现:SQL Query to convert cron expression to date/time format

它不完整,因为它不处理 cron 表达式的各个字段的复杂表达式,如 */5 或 5,10,15,但这是我要开始的地方。

您可能需要调整一些函数来匹配您的 SQL 方言,但这似乎可行,您在这里唯一输入的是 ETL_CRON 的值,输出是布尔值 RUN_ETL. 示例 returns 在星期一、星期二、星期三、星期四和星期五的每小时 3 点到 59 点的每 12 分钟为 TRUE。:

select
      '3/12 2-22 * * 1,2,3,4,5'   as etl_cron
    , split_part(etl_cron,' ',1) as etl_minute
    , split_part(etl_cron,' ',2) as etl_hour
    , split_part(etl_cron,' ',3) as etl_daymonth
    , split_part(etl_cron,' ',4) as etl_month
    , split_part(etl_cron,' ',5) as etl_dayweek
    , convert_timezone('Europe/Amsterdam',current_timestamp) as etl_datetime
    , case 
        when etl_minute = '*' then true
        when contains(etl_minute,'-') then minute(etl_datetime) between split_part(etl_minute,'-',1) and split_part(etl_minute,'-',2)
        when contains(etl_minute,'/') then mod(minute(etl_datetime),split_part(etl_minute,'/',2)) - split_part(etl_minute,'/',1) = 0
        else array_contains(minute(etl_datetime)::varchar::variant, split(etl_minute,','))
      end as run_minute
    , case
        when etl_hour = '*' then true
        when contains(etl_hour,'-') then hour(etl_datetime) between split_part(etl_hour,'-',1) and split_part(etl_hour,'-',2) 
        when contains(etl_hour,'/') then mod(hour(etl_datetime),split_part(etl_hour,'/',2)) - split_part(etl_hour,'/',1) = 0
        else array_contains(hour(etl_datetime)::varchar::variant, split(etl_hour,','))
      end as run_hour
    , case 
        when etl_daymonth = '*' then true
        when contains(etl_daymonth,'-') then day(etl_datetime) between split_part(etl_daymonth,'-',1) and split_part(etl_daymonth,'-',2) 
        when contains(etl_daymonth,'/') then mod(day(etl_datetime),split_part(etl_daymonth,'/',2)) - split_part(etl_daymonth,'/',1) = 0
        else array_contains(day(etl_datetime)::varchar::variant, split(etl_daymonth,','))
      end as run_daymonth
    , case 
        when etl_month = '*' then true
        when contains(etl_month,'-') then month(etl_datetime) between split_part(etl_month,'-',1) and split_part(etl_month,'-',2) 
        when contains(etl_month,'/') then mod(month(etl_datetime),split_part(etl_month,'/',2)) - split_part(etl_month,'/',1) = 0
        else array_contains(month(etl_datetime)::varchar::variant, split(etl_month,','))
      end as run_month
    , case 
        when etl_dayweek = '*' then true
        when contains(etl_dayweek,'-') then dayofweek(etl_datetime) between split_part(etl_dayweek,'-',1) and split_part(etl_dayweek,'-',2) 
        when contains(etl_dayweek,'/') then mod(dayofweek(etl_datetime),split_part(etl_dayweek,'/',2)) - split_part(etl_dayweek,'/',1) = 0
        else array_contains(dayofweek(etl_datetime)::varchar::variant, split(etl_dayweek,','))
      end as run_dayweek
    , run_minute and run_hour and run_daymonth and run_month and run_dayweek as run_etl;