我应该将 org.joda.time.YearMonth 作为日期存储还是作为 PostgreSQL 中的两列存储?

Should I store a org.joda.time.YearMonth as date or as two columns in PostgreSQL?

我在我的数据模型中存储特定月份(含年份)的值。为简单起见,假设我将像这样存储几个月的平均温度:

|  month  | degreeCelsius |
+---------+---------------|
| 2010-01 |     5.2       |
| 2010-02 |     6         |
| 2010-03 |     6.8       |
|   ...   |     ...       |

现在我想将此数据保存到 PostgreSQL table,我想知道月份要使用哪种数据类型。在我的 Java 应用程序中,我使用 org.joda.time.YearMonth 类型来表示月份。

基本上我有两个想法:要么我使用 date 类型,它也会为 "day" 保存一个值,这将毫无意义。由于月份是 table 的主键,因此重要的是不要为同一个月(但日期不同)插入两个值,并且我可以可靠地搜索特定年份的平均温度 -月。因此,我可以创建一个强制执行日期必须始终为“1”的约束,如果我想要 2010 年 2 月的值,我可以可靠地搜索 2010-02-01

然而,保留日期感觉有点尴尬,查看数据模型的其他人可能会对日期部分感到困惑。所以另一个想法是将该类型拆分为两列:yearmonth。这是否引入了我没有看到的新问题?哪种方案是"better"/我应该注意哪些缺点?

我会建议一个日期,因为这样您可以在 sql 查询中进行日期运算时直接使用它。

请注意,实现您提到的检查约束的一个好方法是 value=date_trunc('month', value)。这样您就可以保证没有与该值关联的时间值。 (编辑:实际上没关系,因为 postgres 有一个不接受时间值的日期数据类型。我习惯于使用 oracle,我们遇到的 "date" 数据类型实际上是一个时间戳)

我不喜欢为此使用日期。在这种情况下,日期违反了最小惊喜原则。

所以我会将月份存储为一列或两列。如果您需要进行日期运算——根据我的经验,这不太可能——您可以通过连接和强制转换获得日期或时间戳。

需要注意的两个关键问题是限制和特权。

一栏

将有效月份存储在 table 中,并为其设置外键引用。

create table months (
  cal_month char(7) primary key
);

insert into months 
select left(generate_series('2010-01-01'::timestamp, 
                            '2100-12-01'::timestamp, 
                            '1 month')::text, 7);

您需要撤销 几乎 所有人对 table 的权限。

create table avg_temps (
  cal_month char(7) primary key references months (cal_month),
  temp_c numeric(3, 1)
);

insert into avg_temps values
('2010-01', 5.2), ('2010-02', 6), ('2010-03', 6.8);

两列

您可以将基础 table 放在不同的架构中。 (也适用于一栏方案。)这样可以更简单地控制权限。

create schema temperature;

create table temperature.avg_temps (
  cal_year integer not null
    check (cal_year between 2010 and 2100),
  cal_month integer not null
    check (cal_month between 1 and 12),
  temp_c numeric(3, 1)
);

insert into avg_temps values
(2010, 1, 5.2), (2010, 2, 6), (2010, 3, 6.8);

并使用 public 架构中的视图使其看起来整洁。

create view public.avg_temps as 
select cal_year || '-' || lpad(cal_month::text, 2, '0') as cal_month, temp_c
from temperature.avg_temps;

我自己更喜欢单栏方法。