有多种解决方案可用。选择最适合您的用例的一种:
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_start 和 possible_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 的最小精度限制为一天。基于timestamp 或timestamptz 的解决方案不需要它。)
最后可能的日期可以用(possible_start + possible_length - interval '1 day')::date 表示。或者,整个范围为(对于daterange 索引):daterange(possible_start, (possible_start + possible_length)::date)(范围在其末尾隐含排他性)。
http://rextester.com/AWIO2403