在SQL中表示"X of Y"

Chr*_*dal 6 sql database-design

在我的数据库中,我有许多必修课程.有些是选修课.但是,有第三种课程:您必须从中选择X课程的列表.每个学习计划的列表(和数字X)是不同的.你会如何表达这种关系?

one*_*hen 5

我觉得有趣的是,接受的答案说:"当问题基本上是问题时,没有办法代表'Y'关系'.在我看来,'X of Y'确实可以使用SQL建模(并在很大程度上强制执行),这是一种建议的方式:

示例场景:参加"法语"课程的学生必须从总共三个可能的组成部分(y)中选择两个组成部分(x).

CREATE TABLE Components
(
 component_name VARCHAR(100) NOT NULL, 
 UNIQUE (component_name)
);

INSERT INTO Components (component_name) VALUES 
('Oral'), 
('Writing'), 
('Vocab'), 
('Databases');
Run Code Online (Sandbox Code Playgroud)

很明显,"数据库"不属于法语课程,所以我们需要一个表格供课程设计者来建模课程[这些表格有很多相关的候选键,所以为了清楚起见我会在CREATE TABLE声明的"底部"定义它们) :

CREATE TABLE XofYCourses
(
 course_name VARCHAR(100) NOT NULL, 
 x_components_choice_tally INTEGER NOT NULL 
    CHECK (x_components_choice_tally > 0), 
 y_components_tally INTEGER NOT NULL
    CHECK (y_components_tally > 0), 
 CHECK (x_components_choice_tally < y_components_tally),
 UNIQUE (course_name), 
 UNIQUE (course_name, y_components_tally), 
 UNIQUE (course_name, x_components_choice_tally)
);


INSERT INTO XofYCourses (course_name, y_components_tally, 
x_components_choice_tally) VALUES 
('French', 2, 3);
Run Code Online (Sandbox Code Playgroud)

以上允许我们模拟法语课程的"三分之二"属性.现在我们需要一个表来模拟该课程的三个可能组成部分:

CREATE TABLE XofYCourseComponents
(
 course_name VARCHAR(100) NOT NULL, 
 y_components_tally INTEGER NOT NULL, 
 FOREIGN KEY (course_name, y_components_tally)
    REFERENCES XofYCourses (course_name, y_components_tally), 
 component_sequence INTEGER NOT NULL
    CHECK (component_sequence > 0), 
 component_name VARCHAR(100) NOT NULL 
    REFERENCES Components (component_name), 
 CHECK (component_sequence <= y_components_tally), 
 UNIQUE (course_name, component_sequence), 
 UNIQUE (course_name, component_name) 
);

INSERT INTO XofYCourseComponents (course_name, 
component_sequence, y_components_tally, component_name) 
VALUES 
('French', 1, 3, 'Oral'), 
('French', 2, 3, 'Writing'), 
('French', 3, 3, 'Vocab');
Run Code Online (Sandbox Code Playgroud)

现在报名参加.比利想做法语课程......

CREATE TABLE Students
(
 student_name VARCHAR(20) NOT NULL, 
 UNIQUE (student_name)
);

INSERT INTO Students (student_name) VALUES ('Billy');
Run Code Online (Sandbox Code Playgroud)

......并选择'口头'和'词汇':

CREATE TABLE XofYCourseComponentChoices
(
 student_name VARCHAR(20) NOT NULL
    REFERENCES Students (student_name), 
 course_name VARCHAR(100) NOT NULL, 
 x_components_choice_tally INTEGER NOT NULL, 
 FOREIGN KEY (course_name, x_components_choice_tally)
    REFERENCES XofYCourses (course_name, x_components_choice_tally), 
 component_name VARCHAR(100) NOT NULL, 
 FOREIGN KEY (course_name, component_name)
    REFERENCES XofYCourseComponents (course_name, component_name), 
 x_component_sequence INTEGER NOT NULL
    CHECK (x_component_sequence > 0), 
 CHECK (x_component_sequence <= x_components_choice_tally), 
 UNIQUE (student_name, course_name, component_name), 
 UNIQUE (student_name, course_name, x_component_sequence)
);

INSERT INTO XofYCourseComponentChoices (student_name, course_name, 
component_name, x_component_sequence, x_components_choice_tally)
VALUES
('Billy', 'French', 'Oral', 1, 2), 
('Billy', 'French', 'Vocab', 2, 2);
Run Code Online (Sandbox Code Playgroud)

上述结构是一种强制执行最大值的好方法,即法语课程不超过三个组成部分,每个学生不超过两个选项.

然而,它没有做的是确保确切的金额,例如比利不会只选择一个组件.标准SQL有这个问题的解决方案,例如CHECK支持子查询的约束(例如,计算Billy总共有两行......)和DEFERRABLE约束(......但是延迟计数直到提交事务的时间点) .拥有"多重分配"功能会更好.但是,大多数SQL产品都没有这些功能.

这种缺乏对完整解决方案的支持是否意味着我们不做任何事情而只是相信应用程序将不会编写无效数据?当然不是!

一个好的临时方法是撤销基表中的权限并提供帮助程序存储过程,例如注册一个学生,该学生将他们选择的课程组件作为参数:计数在INSERTs 之后完成,如果它违反了数据规则(例如少于两个法语)然后回滚事务并返回错误.