PostgreSQL - 一对一 - 哪种方法是 "better"?
PostgreSQL - One-To-One - which approach is "better"?
我想在两个 table 之间创建一对一关系:teachers
和 office
(教师是关系的所有者)。
在网上查了一下,我发现了两种不同的方法:
第一个,在办公室table定义一个foreign key
并添加约束NOT NULL
和UNIQUE
。基本上,具有 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 关系 您需要 有两个外键:一个从 teachers
到 office
,另一个从 office
到 teachers
。否则,应用程序可能会将数据插入 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;
请看这里
我想在两个 table 之间创建一对一关系:teachers
和 office
(教师是关系的所有者)。
在网上查了一下,我发现了两种不同的方法:
第一个,在办公室table定义一个foreign key
并添加约束NOT NULL
和UNIQUE
。基本上,具有 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 关系 您需要 有两个外键:一个从 teachers
到 office
,另一个从 office
到 teachers
。否则,应用程序可能会将数据插入 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;
请看这里