Is there a way to keep the entry on Tuesday and Wednesday on the same line, or do I have two entries?
There are several ways to store multiple time ranges on a single line. @bma has already provided a couple of them. This can be useful to save disk space with very simple temporary patterns. A clean, flexible, and βnormalizedβ approach is to save one line per time range .
What is the best way to keep the day and time?
Use timestamp (or timestamptz if you have to deal with multiple time zones). Choose an arbitrary "interim" week and simply ignore the portion of the date using the day and time aspects of the timestamp . The simplest and fastest in my experience, and all date and time related health checks are built-in automatically. I use a range starting from 1996-01-01 00:00 for several similar applications for two reasons:
- The first 7 days of the week coincide with the day of the month (for
sun = 7 ). - This is the last leap year (available February 29 for annual models) at the same time.
Range type
Since you are actually dealing with time ranges (not just day and time), I suggest using the built-in range type tsrange (or tstzrange ). The main advantage: you can use the arsenal of built-in range functions and operators . Requires Postgres 9.2 or later .
For example, an exception restriction restriction can be built on it (implemented internally using the fully functional GiST index, which can provide additional benefits) to eliminate overlapping time ranges. Consider this answer:
- Prevent contiguous / overlapping records with EXCLUDE in PostgreSQL
For this specific exception constraint (without overlapping ranges for each event), you need to include the integer event_id column in the constraint, so you need to install the additional btree_gist module. Install once for each database using:
CREATE EXTENSION btree_gist;
Or you may have one simple CHECK constraint to limit the allowed time period using the "range contained" operator <@ .
It might look like this:
CREATE TABLE event (event_id serial PRIMARY KEY, ...); CREATE TABLE schedule ( event_id integer NOT NULL REFERENCES event(event_id) ON DELETE CASCADE ON UPDATE CASCADE , t_range tsrange , PRIMARY KEY (event_id, t_range) , CHECK (t_range <@ '[1996-01-01 00:00, 1996-01-09 00:00)')
For a weekly schedule, use the first seven days, Mon-Sun, or whatever suits you. Monthly or annual schedules in the same way.
How to extract day of the week, time, etc.
@CDub provided a module to handle this at the end of Ruby. I cannot comment on this, but you can also do everything in Postgres, with flawless performance.
SELECT ts::time AS t_time
Or similarly for range types:
SELECT EXTRACT(DOW FROM lower(t_range)) AS dow_from -- day of week lower bound , EXTRACT(DOW FROM upper(t_range)) AS dow_to -- same for upper , lower(t_range)::time AS time_from -- start time , upper(t_range)::time AS time_to -- end time FROM schedule;
SQL Fiddle
ISODOW instead of DOW for EXTRACT() returns 7 instead of 0 for Sunday. There is a long list of what you can extract.
This answer shows how to use the range type operator to calculate the total duration for time ranges (last chapter):
- Calculate working hours between two dates in PostgreSQL