我正在收集存储在数据库中的日期,但必须考虑到用户可能不知道确切日期的事实。我想提供只输入月份和年份的选项。将这些值存储在数据库中的最佳方法是什么?
如果我在没有白天的情况下使用Date对象,然后将其作为日期存储在数据库中,则默认为该月的第1天,这将是不准确的。我有想法可能将日,月和年分别作为整数存储,然后在模型上有一个返回Date对象的方法,以及当天是否准确(即由用户输入而不是仅仅默认为系统)。
虽然看起来有点乱,有没有更好的方法呢?
感谢。
答案 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
的最低精度限制为一天。基于interval
或timestamp
的解决方案不需要它。)
最后一个可能的日期可以用timestamptz
表示。或者,整个范围为(对于(possible_start + possible_length - interval '1 day')::date
索引):daterange
(范围在其末尾隐式排除)。
答案 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)