【问题标题】:Rails - storing a date when the day is optionalRails - 当日期是可选的时存储日期
【发布时间】:2017-03-28 10:20:38
【问题描述】:

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

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

这似乎有点乱,有没有更好的方法来做到这一点?

谢谢。

【问题讨论】:

  • 您可以将日期存储在一个列中,将精度存储在另一列中,例如“2017-03-28”、“day”和“2017-03-01”、“month”,这样您就可以区分它来自确切的日期“2017-03-01”,“天”

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


【解决方案1】:

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

1.单个日期部分字段

您已经尝试过这个想法。好像是这样的:

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

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

请注意,所有字段都是NULL-able。如果你至少想知道一些信息,你可以year NOT NULL

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

这种模型的最大缺点是难以索引。你可以索引 f.ex。 make_date(year, coalesce(month, 1), coalesce(day, 1)) 但在查询中使用它相当不方便。此外,要禁止某些没有意义的值组合(例如,给定一年零一天,但不是一个月),您也应该添加一个(非常长的)CHECK 约束,例如。

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 真的只是一个样本,则很难按日期选择(例如,当2017-03 的整个月份应该以行,样本日期甚至可以是2017-03-28)。当您将第一个日期用作sample_date(从它可以采用的值,基于sample_precision)时,事情会变得稍微容易一些。但是为了完整性,需要以下CHECK 约束:

CHECK (date_trunc(sample_precision::text, sample_date)::date = sample_date)

(稍后将详细介绍如何进一步改进。)

3.可能范围

您可以存储可能的日期范围。使用 possible_startpossible_end 或使用 PostgreSQL 的 daterange type

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 值是准确的。你现在可以查询两个不同的东西:

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

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

这样做的好处是您不受月份范围的限制。您可以使用任何长度的范围。它唯一的缺点是范围必须是连续的。

2。 + 3. ?

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

create table t(
  possible_start  date,
  possible_length interval day
)

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

day 限定符将interval 的最小精度限制为一天。基于timestamptimestamptz 的解决方案不需要它。)

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

http://rextester.com/AWIO2403

【讨论】:

  • 感谢您的全面回答,在尝试了解您提供的每个解决方案时,我学到了很多东西。因为我不需要索引日期并且只有一天可能会丢失,所以我将使用第一个选项并单独存储值。再次感谢!
【解决方案2】:

您可以在一列中存储日期,在另一列中存储精度,要比较值,您可以使用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)

【讨论】:

  • 感谢您提供此解决方案,我之前没有遇到过 date_trunc
猜你喜欢
  • 2014-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-03
  • 2020-12-07
  • 1970-01-01
  • 1970-01-01
  • 2016-12-24
相关资源
最近更新 更多