存储星期几和时间?

bsi*_*qui 6 ruby postgresql activerecord database-design ruby-on-rails

关于在数据库中存储星期和时间的问题,我有两个部分的问题.我正在使用Rails 4.0,Ruby 2.0.0和Postgres.

我有某些事件,这些事件都有一个时间表.例如,对于"Skydiving"活动,我可能有周二,周三和下午3点.

  1. 我有办法将星期二和星期三的记录存储在一行中,还是应该有两条记录?
  2. 存储日期和时间的最佳方式是什么?有没有办法存储星期几和时间(不是日期时间),还是应该是单独的列?如果它们应该分开,我将如何存储一周中的哪一天?我想将它们存储为整数值,0表示星期日,1表示星期一,因为这就是wdayTime类的方法.

任何建议都会非常有用.

Erw*_*ter 9

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

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

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

使用timestamp(或者timestamptz如果你必须处理多个时区).选择任意"分段"周,并在使用日期和时间方面时忽略日期部分timestamp.我的经验最简单,最快速,并且所有与日期和时间相关的完整性检查都是自动内置的.我使用从1996-01-01 00:00几个类似应用程序开始的范围有两个原因:

  • 一周的前7天与该月的日期一致(for sun = 7).
  • 这是最近的闰年(同时提供2月29日的年度模式).

范围类型

由于您实际上处理时间范围(不仅仅是"日期和时间"),我建议使用内置范围类型tsrange(或tstzrange).一个主要优点:您可以使用内置范围函数和运算符的库.需要Postgres 9.2或更高版本.

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

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

CREATE EXTENSION btree_gist;  -- once per db
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用一个简单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
);
Run Code Online (Sandbox Code Playgroud)

对于每周时间表,使用前七天,周一至周日或任何适合您的方式.每月或每年的时间表以类似的方式.

如何提取星期,时间等?

@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)
Run Code Online (Sandbox Code Playgroud)

或类似的范围类型:

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;
Run Code Online (Sandbox Code Playgroud)

SQL小提琴.

ISODOW而不是DOW为EXTRACT()回报7,而不是0星期天.您可以提取的内容很长.

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