PostgreSQL - 一对一 - 哪种方法是 "better"?

PostgreSQL - One-To-One - which approach is "better"?

我想在两个 table 之间创建一对一关系:teachersoffice(教师是关系的所有者)。 在网上查了一下,我发现了两种不同的方法:

第一个,在办公室table定义一个foreign key并添加约束NOT NULLUNIQUE。基本上,具有 UNIQUE 约束的强制性一对多关系: 像这样:

CREATE TABLE office(
    id SERIAL PRIMARY KEY,
    floor SMALLINT CHECK (floor > 0 AND floor <= 10),
    size SMALLINT CHECK (size > 0),
    teacher_id INTEGER NOT NULL UNIQUE REFERENCES teachers(teacher_id)
);

第二个我们在办公室合并 primary key foreign key table 成一行。像这样:

CREATE TABLE office(
    id SERIAL PRIMARY KEY REFERENCES teachers(teacher_id),
    floor SMALLINT CHECK (floor > 0 AND floor <= 10),
    size SMALLINT CHECK (size > 0)
);

从功能上讲,在我看来这两种方法的工作原理是一样的。第一个似乎使用了一点额外的内存,因为它多了一行。

我的问题是:每种方法的优缺点是什么? 一种方法比另一种“更好”吗?

如果这确实是 1-1 关系,为什么不在 teachers table 中包含描述办公室的列?

您不想这样做可能是有原因的。一个重要的原因是 office 可以在没有 teacher 的情况下存在。例如,如果您的学校没有暑期班,那么办公室将继续存在。

这表明办公室独立于教师。所以,你想要一个单独的 table 和一个单独的主键。

在您的第二个模型中——其中主键 teachers 的外键——您实际上是在说办公室是一种教师,不是一个单独的实体。

不过有两个注意事项:

首先,Postgres 不推荐 generated always as identity 而不是 serial。第二个 table 不应该为主键使用 serial 列;应该是 int:

CREATE TABLE office (
    id int PRIMARY KEY REFERENCES teachers(teacher_id),
    floor SMALLINT CHECK (floor > 0 AND floor <= 10),
    size SMALLINT CHECK (size > 0)
);

将自动分配的列作为外键引用没有意义。

我会选择第二种方法,为主键共享完全相同的值。对于 office table 主键不应自动生成 (serial) 而只是 int.

例如你可以这样做:

create table teachers (
  id int primary key not null,
  name varchar(20) not null
);

create table office (
  id int primary key references teachers (id),
  floor smallint check (floor > 0 and floor <= 10),
  size smallint check (size > 0)
);

alter table teachers
add constraint fk_uq1_teachers_office
foreign key (id) references office (id) deferrable initially deferred;

然后插入数据可能如下所示:

begin transaction;

insert into teachers (id, name) values (100, 'Mary');

insert into office (id, floor, size) values (100, 3, 850);

commit;

请注意,为了强制执行 1-1 关系 您需要 有两个外键:一个从 teachersoffice,另一个从 officeteachers。否则,应用程序可能会将数据插入 teachers 而忘记插入 office。幸运的是,您使用的 PostgreSQL 实现了 标准 SQL 约束可延迟性的特性,如上所示。

参见 DB Fiddle 中的 运行 示例。

Bi-directional 强制外键可能成为维护的噩梦。另一种选择是将 teacher to office 建模为 m:m,然后放置部分唯一索引来约束它们。这给了你很大的灵活性,因为教师和办公室都是独立的实体,如果需要,你有教师和办公室关系的历史记录。就目前而言,除非分配一名教师,否则办公室不可能存在。 (您可以使 office.teacher null-able。至少应该。)。采用 m:m 结构,您的 table 定义变为:

create table teachers (
  id int primary key not null,
  name varchar(20) not null
);
 
create table offices (
  id int primary key, 
  floor smallint check (floor > 0 and floor <= 10),
  size smallint check (size > 0)
);

 
create table teacher_offices
      (id             integer generated always as identity
      ,teacher_id     integer not null
      ,office_id      integer not null
      ,effective_date date    not null default now()::date
      ,end_date       date  
      ,constraint teacher_office_pk 
                  primary key(id)
      ,constraint to2office_fk  
                  foreign key (office_id)
                  references offices (id) 
                  on delete cascade
      ,constraint to2teacher_fk  
                  foreign key (teacher_id)
                  references teachers (id)
                  on delete cascade
      );
     
-- create the partial unique index     
create unique index office_already_occupied_by_teacher
          on teacher_offices (office_id)
          where end_date is null;
         
create unique index teacher_already_has_office
          on teacher_offices (teacher_id)
          where end_date is null; 

    

full example.

请看这里