使用 Javascript 和 PLV8 在 Postgresql 中计算重复事件

jdi*_*n04 6 javascript sql postgresql

我已经阅读了大约 20 篇关于重复事件数据建模选项的不同帖子,并最终确定了一个实现,但在最终细节方面仍然需要一些帮助。

首先,这里描述了我要完成的任务和要求:

  • 我正在使用 Postgres 9.4
  • 我将它托管在 Amazon RDS 上,它只允许有限数量的预先审查的扩展。

显然,我的应用程序的很大一部分是基于调度的。具体来说,我系统的这个组件允许企业客户安排家庭服务的约会。这些约会可以是单次发生的,也可以是重复发生的。虽然这些约会是典型的,因为它们有开始/结束时间等,但每个约会也有与之相关的数据。该数据可能包括诸如入住时间、退房时间、备注等内容。

为了存储重复约会,我的选择是要么在预定时间点生成每个实例,然后在这些实例运行时在数据库中生成新实例,要么即时计算要返回的这些实例。我最终选择了后者,因为我认为从长远来看,该实现更清洁,更易于维护。

话虽如此,根据这篇文章的见解,我选择将我的重复模式存储为 RRULE 字符串,并使用 PGV8 和rrule.js库进行任何计算。那篇文章提到了物化视图的使用(阅读了它们,但从未使用过),但我不确定它是否适用于我的情况,因为我必须在定期约会更改或创建时重新生成这些。似乎我还需要为每个业务创建一个实体化视图,并且不确定这会如何影响存储/性能,因为可能有 1000 个业务。如果您对此有任何见解,请告诉我。

这个想法是有一个表,Appointment,其中包含与约会的实际日期、时间和重复(如果适用)相关的数据。它将至少包含以下字段:

  • 开始日期
  • 结束日期
  • 重复模式 (RRULE)
  • 例外
  • 服务编号

然后,第二个表,AppointmentData,将存储有关约会本身的任何元数据。例如,它可能包含以下字段:

  • 预约 ID(FK 给家长)
  • 笔记
  • 登记时间
  • 退房时间
  • 等等。

AppointmentData当实例只会产生Appointment实际上是由服务提供商启动。

一般来说,我只需要能够在任何给定时间检索 31 天或更少的约会(除了检索单个实例)。也就是说,我的想法是能够将开始和结束日期传递给数据库,该数据库将找到该范围内所有约会的单次出现。此外,对于包含重复模式的任何记录,我将使用我的 PLV8 函数返回该范围内的日期列表。该rrule.js 库有一个函数可以返回重复模式 ( rule.between(new Date(2012, 7, 1), new Date(2012, 8, 1))) 的所有日期。

这是我有点磕磕绊绊的地方。现在我在数据库中有一个可以动态计算重复日期的函数,我有点不清楚如何将这些与单个事件“融合”在一起并将它们作为单个结果集返回。请注意,对于每个重复出现的实例,我还需要返回表中的所有列Appointment,例如serviceID.

如果有什么不清楚的,请告诉我。

提前致谢!

jdi*_*n04 3

对于那些想要做类似事情的人,我提出了以下建议:

首先,使用 rrule.js 从 iCAL RRule 字符串生成递归的函数:

CREATE OR REPLACE FUNCTION public.generate_recurrences(
  recurrence_pattern CHARACTER VARYING,
  start_date date,
  end_date date)
 RETURNS SETOF TEXT
 LANGUAGE plv8
 IMMUTABLE STRICT
AS $function$

  // parse the RRULE string
  var rule = RRule.fromString(recurrence_pattern);

  // return all occurrences between start date and end date
  var recurrences = rule.between(start_date, end_date);

  for(var i = 0; i < recurrences.length; i++) {
     plv8.return_next(new Date(recurrences[i]).toISOString());
  }

$function$
Run Code Online (Sandbox Code Playgroud)

最后,该函数获取非重复约会的任何实例,并将它们与上述函数生成的重复实例合并:

CREATE OR REPLACE FUNCTION recurring_events_for(
   for_business_id INTEGER,
   range_start DATE,
   range_end   DATE
)
   RETURNS SETOF appointment
   LANGUAGE plpgsql STABLE
   AS $BODY$
DECLARE
   appointment appointment;
   recurrence  TIMESTAMPTZ;
   appointment_length INTERVAL;
BEGIN
   FOR appointment IN
       SELECT *
         FROM appointment
        WHERE business_id = for_business_id
          AND (
                  recurrence_pattern IS NOT NULL
              OR  (
                     recurrence_pattern IS NULL
                 AND scheduled_start_time BETWEEN range_start AND range_end
              )
          )
    LOOP
       IF appointment.recurrence_pattern IS NULL THEN
         RETURN NEXT appointment;
         CONTINUE;
       END IF;

       appointment_length := appointment.scheduled_end_time - appointment.scheduled_start_time;

       FOR recurrence IN
           SELECT *
             FROM generate_recurrences(
                      appointment.recurrence_pattern,
                      range_start,
                      range_end
             )
       LOOP
           EXIT WHEN recurrence::date > range_end::date;
           CONTINUE WHEN recurrence::date < range_start::date AND recurrence::date > range_end::date;
           appointment.scheduled_start_time := recurrence;
           appointment.scheduled_end_time := recurrence + appointment_length;
           RETURN NEXT appointment;
       END LOOP;
   END LOOP;
   RETURN;
END;
$BODY$;
Run Code Online (Sandbox Code Playgroud)

上面的结果是一个结果集,其中包含两个单一事件的约会记录,但也动态生成重复事件的完整约会记录(只是日期)。

  • 你好@jdixon04,我知道这是一篇旧帖子,但是,你能告诉我们为什么你将渲染事件移到客户端以及你使用了什么方法吗? (2认同)