Postgresql – Procedure to read from crontab like table and populate another one periodically

performancepostgresqlquery-performancestored-procedures

Postgres 9.3
Debian 7

I'm working on a mobile game project (poker like) where an admin user can register a recurrent tournament. As it's recurrent, I modeled it crontab like as follows.

challenge_schedule
+-----------------------+---------+------------------------------------------------------------------------------------+---------+--------------+-------------+
|        Column         |  Type   |                                     Modifiers                                      | Storage | Stats target | Description |
+-----------------------+---------+------------------------------------------------------------------------------------+---------+--------------+-------------+
| challenge_schedule_id | integer | not null default nextval('challenge_schedule_challenge_schedule_id_seq'::regclass) | plain   |              |             |
| challenge_type_id     | integer | not null                                                                           | plain   |              |             |
| game_type_id          | integer | not null                                                                           | plain   |              |             |
| quorum                | integer | not null                                                                           | plain   |              |             |
| fee                   | integer |                                                                                    | plain   |              |             |
| winnings              | integer |                                                                                    | plain   |              |             |
| frequency_minute      | integer |                                                                                    | plain   |              |             |
| frequency_hour        | integer |                                                                                    | plain   |              |             |
| frequency_dom         | integer |                                                                                    | plain   |              |             |
| frequency_month       | integer |                                                                                    | plain   |              |             |
| frequency_dow         | integer |                                                                                    | plain   |              |             |
| frequency_year        | integer |                                                                                    | plain   |              |             |
| description           | varchar |                                                                                    |
+-----------------------+---------+------------------------------------------------------------------------------------+---------+--------------+-------------+

And I need to create a job (procedure) that will populate a queue of the next tournaments in the history table as follows.

challenge_history
+----------------------+-----------------------------+----------------------------------------------------------------------------------+----------+--------------+-------------+
|        Column        |            Type             |                                    Modifiers                                     | Storage  | Stats target | Description |
+----------------------+-----------------------------+----------------------------------------------------------------------------------+----------+--------------+-------------+
| challenge_history_id | integer                     | not null default nextval('challenge_history_challenge_history_id_seq'::regclass) | plain    |              |             |
| challenge_type_id    | integer                     |                                                                                  | plain    |              |             |
| game_type_id         | integer                     |                                                                                  | plain    |              |             |
| start_time           | timestamp without time zone |                                                                                  | plain    |              |             |
| end_time             | timestamp without time zone |                                                                                  | plain    |              |             |
| fee                  | integer                     |                                                                                  | plain    |              |             |
| winnings             | integer                     |                                                                                  | plain    |              |             |
| special              | boolean                     |                                                                                  | plain    |              |             |
| canceled_time        | timestamp without time zone |                                                                                  | plain    |              |             |
| canceled_reason      | character varying(255)      |                                                                                  | extended |              |             |
+----------------------+-----------------------------+----------------------------------------------------------------------------------+----------+--------------+-------------+

In other words, if I have only a daily tournament on schedule and run it to process the next 7 days, it needs to create 7 rows with the correct start_time on each. Consider that in a real world it will have a lot of recurrent tournaments daily, weekly, monthly… And it needs to populate everything for the next X days (7 in the example).

I was wondering the best way to solve this and want thoughts/ideas about.

Best Answer

After some time i figured out how to do this. Lets put as sample data 2 recurrent tournaments:

One daily at 16:20 and another every sunday at 14:00

insert into challenge_schedule(challenge_type_id, game_type_id, quorum, frequency_minute, frequency_hour, frequency_dom, frequency_month, frequency_dow, frequency_year, description) values (1, 4, 10, 20, 16, null, null, null, null, 'Daily 4:20 PM');
insert into challenge_schedule(challenge_type_id, game_type_id, quorum, frequency_minute, frequency_hour, frequency_dom, frequency_month, frequency_dow, frequency_year, description) values (1, 4, 10, 0, 14, null, null, 1, null, 'Sundays 14:00 PM');

My solution was to make a full join between the schedule and every minute timestamp (as it's the smallest division) from now until the days limit and comparing each timestamp field with each schedule as follows (In the example I'm calling the query to process 5 days (1440 minutes * 5)):

SELECT
  gs.start_time, cs.description
FROM
  challenge_schedule cs,
  (select date_trunc('minute', NOW()) + (gs||' minute')::INTERVAL as start_time from generate_series(1, 1440*2) gs) gs
WHERE
  (cs.frequency_minute = to_char(gs.start_time, 'MI')::INTEGER OR cs.frequency_minute IS NULL)
  AND (cs.frequency_hour = to_char(gs.start_time, 'HH24')::INTEGER OR cs.frequency_hour IS NULL)
  AND (cs.frequency_dom = to_char(gs.start_time, 'DD')::INTEGER OR cs.frequency_dom IS NULL)
  AND (cs.frequency_month = to_char(gs.start_time, 'MM')::INTEGER OR cs.frequency_month IS NULL)
  AND (cs.frequency_dow = to_char(gs.start_time, 'D')::INTEGER OR cs.frequency_dow IS NULL)
  AND (cs.frequency_year = to_char(gs.start_time, 'YYYY')::INTEGER OR cs.frequency_year IS NULL)
ORDER BY
  gs.start_time asc
;

In the comparisons each field needs to be equals to the generated timestamp field OR NULL that means every.

The resultset was:

2015-07-22 16:20:00 'Daily 4:20 PM'
2015-07-23 16:20:00 'Daily 4:20 PM'
2015-07-24 16:20:00 'Daily 4:20 PM'
2015-07-25 16:20:00 'Daily 4:20 PM'
2015-07-26 14:00:00 'Sundays 14:00 PM'
2015-07-26 16:20:00 'Daily 4:20 PM'

Hope it helps somebody.