Rails - 当日可选时存储日期

时间:2017-03-28 10:20:38

标签: sql ruby-on-rails postgresql activerecord ruby-on-rails-5

我正在收集存储在数据库中的日期,但必须考虑到用户可能不知道确切日期的事实。我想提供只输入月份和年份的选项。将这些值存储在数据库中的最佳方法是什么?

如果我在没有白天的情况下使用Date对象,然后将其作为日期存储在数据库中,则默认为该月的第1天,这将是不准确的。我有想法可能将日,月和年分别作为整数存储,然后在模型上有一个返回Date对象的方法,以及当天是否准确(即由用户输入而不是仅仅默认为系统)。

虽然看起来有点乱,有没有更好的方法呢?

感谢。

2 个答案:

答案 0 :(得分:2)

有多种解决方案可供选择。选择最适合您的用例的那个:

<强> 1。个别日期 - 部分字段

您已经尝试过这个想法。看起来像这样:

create table t(
  year  int,
  month int,
  day   int
)

2017-03的一个月可以用(2017, 3, NULL)表示。

请注意,所有字段均为NULL。如果您想要至少一些信息,可以year NOT NULL

使用此模型,您必须使用客户端逻辑来构造某种类似日期的对象以供进一步使用。

这种模式的最大缺点是很难索引。你可以索引f.ex. make_date(year, coalesce(month, 1), coalesce(day, 1))但在查询中使用它相当不方便。另外,为了禁止一些没有意义的值组合(f.ex.给出一年和一天,而不是一个月),你应该添加一个(非常长的)CHECK约束,f.ex。

CHECK (CASE
  WHEN year IS NULL THEN month IS NULL AND day IS NULL
  ELSE CASE WHEN month IS NULL THEN day IS NULL END
END)

<强> 2。样本日期和精确度

create table t(
  sample_date      date,
  sample_precision date_precision -- enum of f.ex. 'year', 'month', 'day'
)

2017-03的一个月可以用('2017-03-28', 'month')表示。

这并不需要长CHECK个约束,但如果sample_date只是一个样本,那么很难按日期选择(f.ex.整个月{{ 1}}应该连续表示,样本日期甚至可以是2017-03)。当您将第一个日期用作2017-03-28时(根据sample_date可以采用的值),事情会变得更容易一些。但是,完整性需要以下sample_precision约束:

CHECK

(稍后将进一步改进这一点。)

第3。可能的范围

您可以存储可能的日期范围。使用CHECK (date_trunc(sample_precision::text, sample_date)::date = sample_date) possible_start或使用PostgreSQL daterange type

possible_end

create table t( possible_start date, possible_end date, -- or possible_range daterange ) 的一个月可以用2017-03表示。

在此('2017-03-01', '2017-03-31')模型中,possible_start = possible_end值是准确的。你现在可以查询两个不同的东西:

  • 确定(包含
  • 在给定日期周围发生了哪些行
  • 在给定日期(相交
  • 周围可能发生哪些行

这两种类型的查询都可以使用带有date的索引。

这样做的好处是你不仅限于月份范围。您可以使用字面上任意长度的范围。唯一的缺点是范围必须是连续的。

<强> 2。 + 3.?

有一个变体,它具有 3。的所有优点,但看起来像 2。interval type

daterange

create table t( possible_start date, possible_length interval day ) 的一个月可以用2017-03表示。

('2017-03-01', '1 month')限定符将day的最低精度限制为一天。基于intervaltimestamp的解决方案不需要它。)

最后一个可能的日期可以用timestamptz表示。或者,整个范围为(对于(possible_start + possible_length - interval '1 day')::date索引):daterange(范围在其末尾隐式排除)。

http://rextester.com/AWIO2403

答案 1 :(得分:1)

您可以将日期存储在一列中,精确度存储在其他列中,为了比较值,您可以使用date_trunc(precision_column,DATE),例如:

t=# create table so36(d date,p text);
CREATE TABLE
t=# insert into so36 select now(),'day';
INSERT 0 1
t=# insert into so36 select '2017-03-01','month';
INSERT 0 1
t=# select *,date_trunc(p,d),date_trunc(p,now()) from so36;
     d      |   p   |       date_trunc       |       date_trunc
------------+-------+------------------------+------------------------
 2017-03-28 | day   | 2017-03-28 00:00:00+00 | 2017-03-28 00:00:00+00
 2017-03-01 | month | 2017-03-01 00:00:00+00 | 2017-03-01 00:00:00+00
(2 rows)