我在问题标题中提出的问题思考起来有点复杂,所以这是我实际的具体问题:
我有四个相关表:课程组、课程、用户和选择。以下规则适用:
我尝试使用以下模式:
-- Many irrelevant fields have been redacted
CREATE TABLE cgroups (name TEXT PRIMARY KEY NOT NULL);
CREATE TABLE courses (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
cgroup TEXT NOT NULL,
FOREIGN KEY(cgroup) REFERENCES cgroups(name)
);
CREATE TABLE users (id TEXT PRIMARY KEY NOT NULL);
CREATE TABLE choices (
PRIMARY KEY (userid, courseid), -- Primary key guarantees unique
userid TEXT NOT NULL,
FOREIGN KEY(userid) REFERENCES users(id),
courseid INTEGER NOT NULL,
FOREIGN KEY(courseid) REFERENCES courses(id),
-- Which course group does this choice belong to? Primary problem here:
-- I want to guarantee that the cgroup here corresponds to the cgroup
-- of the course referenced above.
cgroup TEXT NOT NULL,
FOREIGN KEY(cgroup) REFERENCES cgroups(name),
-- Attempt to ensure that each user can only choose one course from a cgroup
UNIQUE (userid, cgroup)
);
这里的问题是
choice.cgroup
不限于对应于courses[choice.courseid].cgroup
。我该如何表达这个约束呢?或者是否有其他方法可以根据我上面提出的规则设计模式?
以下脚本创建的 schema 确保每一门课程都与一个课程组关联,用户只能选择一次课程,每个组中只能选择一门课程,并且选择的课程和组与课程一致课程定义:
CREATE TABLE cgroups (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
UNIQUE (name)
);
CREATE TABLE courses (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
cgroup_id INTEGER NOT NULL REFERENCES cgroups (id),
name TEXT NOT NULL,
UNIQUE (name),
UNIQUE (id, cgroup_id)
);
CREATE TABLE users (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
UNIQUE (name)
);
CREATE TABLE choices (
user_id INTEGER NOT NULL REFERENCES users (id),
course_id INTEGER NOT NULL,
cgroup_id INTEGER NOT NULL,
PRIMARY KEY (user_id, course_id), -- Primary key guarantees unique
-- referencing (course_id, cgroup_id) ensures that the pair are consistent with the course
FOREIGN key (course_id, cgroup_id) REFERENCES courses (id, cgroup_id),
-- ensure that each user can only choose one course per cgroup
-- column order facilitates efficient searches for users with courses in a specific cgroup
UNIQUE (cgroup_id, user_id)
);
已将合成主键
id
添加到每个表(choices
除外),以便更轻松地更新名称,而无需更新对该名称的每个引用。索引和搜索整数列通常比文本列更有效。由于 choices
本质上是一个 associative 或 bridge 表,因此不需要合成密钥。
这种模式的一个问题是改变课程和团体关联变得很困难。限制用户从组中选择单个课程似乎更像是一个业务问题,而不是数据完整性问题。一般来说,数据库模型应该强制数据完整性并将业务逻辑留给应用程序。