8

I have a two-part question about storing days of the week and time in a database. I'm using Rails 4.0, Ruby 2.0.0, and Postgres.

I have certain events, and those events have a schedule. For the event "Skydiving", for example, I might have Tuesday and Wednesday and 3 pm.

  1. Is there a way for me to store the record for Tuesday and Wednesday in one row or should I have two records?
  2. What is the best way to store the day and time? Is there a way to store day of week and time (not datetime) or should these be separate columns? If they should be separate, how would I store the day of the week? I was thinking of storing them as integer values, 0 for Sunday, 1 for Monday, since that's how the wday method for the Time class does it.

Any suggestions would be super helpful.

4

4 回答 4

11

有没有办法让我将周二和周三的记录存储在一行中,或者我应该有两条记录吗?

有几种方法可以在一行中存储多个时间范围。@bma 已经提供了其中的几个。这对于使用非常简单的时间模式节省磁盘空间可能很有用。干净、灵活和“规范化”的方法是每个时间范围存储一行

存储日期和时间的最佳方式是什么?

使用 a timestamp(或者timestamptz如果可能涉及多个时区)。选择一个任意的“暂存”周,并在使用timestamp. 根据我的经验,最简单和最快的,所有与日期和时间相关的完整性检查都是自动内置的。我将一个范围从1996-01-01 00:00几个类似的应用程序开始使用,原因有两个:

  • 一周的前 7 天与一个月中的某一天重合(对于sun = 7)。
  • 同时也是最近的闰年(年度模式为 2 月 29 日)。

范围类型

由于您实际上是在处理时间范围(不仅仅是“日期和时间”),我建议使用内置范围类型tsrange(​​或tstzrange)。一个主要优势:您可以使用内置的Range Functions 和 Operators库。需要 Postgres 9.2 或更高版本

例如,您可以在此基础上建立一个排除约束(通过可能提供额外好处的全功能 GiST 索引在内部实现),以排除重叠的时间范围。有关详细信息,请考虑此相关答案:

对于这个特定的排除约束(每个事件没有重叠范围),您需要event_id在约束中包含整数列,因此您需要安装附加模块btree_gist。每个数据库安装一次:

CREATE EXTENSION btree_gist;  -- once per db

或者,您可以有一个简单的CHECK约束来使用“范围包含在”运算符来限制允许的时间段<@

可能看起来像这样:

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)')  -- restrict period
 , EXCLUDE USING gist (event_id WITH =, t_range WITH &&)      -- disallow overlap
);

对于每周计划,请使用前 7 天、周一至周日或任何适合您的时间。以类似的方式按月或按年计划。

如何提取星期几、时间等?

@CDub在 Ruby 端提供了一个模块来处理它。我无法对此发表评论,但你也可以在 Postgres 中做任何事情,而且性能无可挑剔。

SELECT ts::time AS t_time           -- get the time (practically no cost)
SELECT EXTRACT(DOW FROM ts) AS dow  -- get day of week (very cheap)

或以类似的方式用于范围类型:

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;

db<>fiddle here
的 sqliddle

ISODOWDOW而不是EXTRACT()退货7而不是0星期天。您可以提取的内容有一长串。

这个相关答案演示了如何使用范围类型运算符来计算时间范围的总持续时间(最后一章):

于 2013-11-11T01:40:28.167 回答
3

根据您的日程安排需求的复杂程度,您可能需要查看RFC 5545iCalendar日程安排数据格式),以了解如何存储数据。

如果您的需求非常简单,那可能是矫枉过正。Postgresql 有许多函数可以将日期和时间转换为您需要的任何格式。

对于存储相对日期和时间的简单方法,您可以按照您的建议将星期几存储为整数,并将时间存储为TIME数据类型。如果您可以在一周中有多个有效日期,您可能需要使用 ARRAY。

例如。

  • ARRAY[2,3]::INTEGER[] = 星期二、星期三为星期几
  • '15:00:00'::TIME = 下午 3 点

[编辑:添加一些简单的例子]

/* Custom the time and timetz range types */
CREATE TYPE timerange AS RANGE (subtype = time);

--drop table if exists schedule;
create table schedule (
    event_id    integer not null, /* should be an FK to "events" table */
    day_of_week integer[],
    time_of_day time,
    time_range  timerange,
    recurring   text CHECK (recurring IN ('DAILY','WEEKLY','MONTHLY','YEARLY'))
);

insert into schedule (event_id, day_of_week, time_of_day, time_range, recurring)
values
(1, ARRAY[1,2,3,4,5]::INTEGER[], '15:00:00'::TIME, NULL, 'WEEKLY'),
(2, ARRAY[6,0]::INTEGER[], NULL, '(08:00:00,17:00:00]'::timerange, 'WEEKLY');

select * from schedule;

 event_id | day_of_week | time_of_day |     time_range      | recurring 
----------+-------------+-------------+---------------------+-----------
        1 | {1,2,3,4,5} | 15:00:00    |                     | WEEKLY
        2 | {6,0}       |             | (08:00:00,17:00:00] | WEEKLY

第一个条目可以理解为:该事件在周一至周五下午 3 点有效,此时间表每周发生一次。
第二个条目可以理解为:该事件在周六和周日上午 8 点到下午 5 点之间有效,每周都会发生。

自定义范围类型“timerange”用于表示时间范围的下限和上限。
'(' 表示“包含”,尾随 ']' 表示“不包含”,或者换句话说“大于或等于上午 8 点且小于下午 5 点”。

于 2013-11-10T21:17:57.843 回答
3

查看 ice_cube gem(链接)。

它可以为您创建一个计划对象,您可以将其保存到您的数据库中。您无需创建两个单独的记录。对于第二部分,您可以根据任何规则创建计划,您不必担心如何将其保存在数据库中。您可以使用 gem 提供的方法从持久的计划对象中获取您想要的任何信息。

于 2013-11-10T21:15:34.430 回答
1

为什么不只存储日期戳,然后使用内置功能Date来获取星期几?

2.0.0p247 :139 > Date.today
 => Sun, 10 Nov 2013 
2.0.0p247 :140 > Date.today.strftime("%A")
 => "Sunday" 

strftime听起来它可以为你做一切。 是它的特定文档。

特别是对于您正在谈论的内容,听起来您需要一个Eventhas_many :schedules,其中 aSchedule将具有start_date时间戳...

于 2013-11-10T21:14:14.867 回答