简体   繁体   English

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

[英]Calculating Recurring Events in Postgresql using Javascript and PLV8

I've read through about 20 different posts regarding the options for modeling recurring event data and have finally settled on an implementation, but still need a bit of help in the final details. 我已经阅读了大约20篇关于模拟重复事件数据的选项的不同帖子,并最终确定了一个实现,但仍需要在最终细节方面提供一些帮助。

First, here's a description of what I'm trying to accomplish and the requirements: 首先,这里是我要完成的内容和要求的描述:

  • I'm using Postgres 9.4 我正在使用Postgres 9.4
  • I'm hosting it on Amazon RDS, which only allows a limited number of pre-vetted extensions. 我在Amazon RDS上托管它,它只允许有限数量的预先审查扩展。

Obviously, a large part of my application is based around scheduling. 显然,我的应用程序的很大一部分是基于调度。 Specifically, this component of my system allows customers of a business to schedule appointments for in-home services. 具体来说,我系统的这个组件允许企业的客户安排家庭服务的约会。 Those appointments can either be single occurrences or recurring. 这些约会可以是单次出现,也可以是经常出现。 While these appointments are typical in the sense that they have a start/end time, etc, each appointment also has data associated with it. 虽然这些约会是典型的,因为它们具有开始/结束时间等,但每个约会也具有与其相关联的数据。 That data may consist of things such as check-in time, check-out time, notes, etc. 该数据可能包括登记入住时间,退房时间,备注等。

For storing recurring appointments, my options were to either generate every instance up to a predetermined point in time and then generate new instances in the database whenever those instances ran up OR to calculate those instances on the fly to be returned. 为了存储定期约会,我的选择是生成每个实例直到预定的时间点,然后在这些实例运行时在数据库中生成新实例或者在运行中计算这些实例以便返回。 I ended up opting for the latter as I thought that implementation to be cleaner and easier to maintain in the long run. 我最终选择了后者,因为我认为从长远来看,实施更清洁,更容易维护。

With that said, using insights from this post , I opted to store my recurrence patterns as RRULE strings and to use PGV8 and the rrule.js library to do any calculations. 话虽如此,使用这篇文章的见解,我选择将我的重复模式存储为RRULE字符串,并使用PGV8和rrule.js库进行任何计算。 That post mentions the use of materialized views (read about them, but never used), but I'm not sure that it would work in my case given that I'd have to regenerate these anytime a recurring appointment changed or was created. 该帖提到了物化视图的使用(阅读它们,但从未使用过),但我不确定它是否适用于我的情况,因为我必须在任何时候更改或创建重复约会时重新生成这些视图。 It also seems like I would need a materialized view for each business and wasn't sure how that might affect storage/performance as there could be 1000's of businesses. 我似乎还需要每个业务的物化视图,并且不确定这可能会影响存储/性能,因为可能有1000个业务。 If you have any insights on this, please let me know. 如果您对此有任何见解,请告诉我。

The idea would be to have a table, Appointment , that contains data relating to the actual date, time and recurrence (if applicable) of the appointment. 这个想法是有一个表, Appointment ,其中包含与Appointment的实际日期,时间和重复(如果适用)有关的数据。 It would contain the following fields at a minimum: 它至少包含以下字段:

  • Start Date 开始日期
  • End Date 结束日期
  • Recurrence Pattern (RRULE) 重复模式(RRULE)
  • Exceptions 例外
  • Service ID 服务ID

Then, a second table, AppointmentData , that would store any meta-data about the appointment itself. 然后,第二个表, AppointmentData ,将存储有关约会本身的任何元数据。 For example, it might contain the following fields: 例如,它可能包含以下字段:

  • Appointment ID (FK to the Parent) 预约ID(父母的FK)
  • Notes 笔记
  • Check-in Time 登记时间
  • Check-out Time 退房时间
  • etc. 等等

The AppointmentData instance would only be created when the Appointment is actually started by the service provider. 只有在服务提供者实际启动Appointment时才会创建AppointmentData实例。

In general, I only need to be able to retrieve 31 days or less of appointments at any given time (in addition to retrieving a single instance). 通常,我只需要能够在任何给定时间检索31天或更少的约会(除了检索单个实例)。 That said, my thought was to be able to pass in a start and end date to the database which would find all single occurrences of appointments that fall within that range. 也就是说,我的想法是能够将开始和结束日期传递给数据库,该数据库将查找属于该范围的所有单次约会。 In addition, for any records that contain a recurrence pattern, I would use my PLV8 function to return a list of dates that fall within that range. 此外,对于包含重复发生模式的任何记录,我将使用我的PLV8函数返回该范围内的日期列表。 The rrule.js library has a function that can return all dates for a recurrence pattern ( rule.between(new Date(2012, 7, 1), new Date(2012, 8, 1)) ). rrule.js库有一个函数可以返回重复模式的所有日期( rule.between(new Date(2012, 7, 1), new Date(2012, 8, 1))rule.between(new Date(2012, 7, 1), new Date(2012, 8, 1)) )。

This is the area where I'm stumbling a bit. 这是我磕磕绊绊的地方。 Now that I have a function in the DB that can calculate recurrence dates on the fly, I'm a bit unclear on how to "meld" these together with the single occurrences and return these as a single result set. 现在我在DB中有一个可以动态计算重复日期的函数,我有点不清楚如何将这些与单个事件“融合”并将它们作为单个结果集返回。 Note that for every recurring instance, I also need to return all of the columns in the Appointment table, such as the serviceID . 请注意,对于每个重复实例,我还需要返回Appointment表中的所有列,例如serviceID

If anything is unclear, please let me know. 如果有任何不清楚的地方,请告诉我。

Thanks in advance! 提前致谢!

For those that are looking to do something similar, here's what I've come up with: 对于那些希望做类似事情的人来说,这就是我想出的:

First, the function that generates the recurrences from an iCAL RRule string using rrule.js: 首先,使用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$

And finally the function that grabs any instances of non-recurring appointments and melds them with recurring instances generated from the above function: 最后是抓住任意非约会约会实例的函数,并将它们与上述函数生成的重复实例融合:

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$;

The result of the above is a result set that contains appointment records for both single occurrences, but also generates full appointment records on the fly for the recurrences (which are just dates). 上述结果是一个结果集,其中包含单个事件的约会记录,但也可以动态生成完整的约会记录(这些记录只是日期)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM