在SQL中表示“X of Y”

时间:2010-07-05 13:47:14

标签: sql database-design

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

8 个答案:

答案 0 :(得分:5)

我觉得有趣的是,接受的答案说,“当问题基本上是问题时,没有办法在关系上代表'X'。在我看来,'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');

显然,'数据库'不属于法语课程,所以我们需要一个表格供课程设计者来建模课程[这些表格有很多相关的候选键,所以为了清楚起见我会在它们的'底部'定义它们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);

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

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');

现在注册。比利想做法语课程......

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

INSERT INTO Students (student_name) VALUES ('Billy');

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

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);

上述结构可以很好地执行最大值,即法语课程不超过三个组件,每个学生不超过两个选项。

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

这是否缺乏对完整解决方案的支持意味着我们什么都不做,只是相信应用程序会避免编写无效数据?当然不是!

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

答案 1 :(得分:4)

这里需要3个表:StudyPrograms,Courses and Components。组件代表构成每个StudyProgram的课程,是课程和StudyPrograms之间的联结表。

每个组件记录都可以包含一个字段,指示课程是否是StudyProgram的必修部分。您还可以包含一个字段,以指示课程是否是可以选择的列表之一。

无法以关系方式表示“X of Y”,您需要在存储过程中使用一些逻辑来确保遵循此业务规则(或者可能在数据访问代码层中,具体取决于您希望如何组织申请)。

答案 2 :(得分:3)

您有两种选择:您可以将数据建模更接近现实,其中一些是单课程要求,另一些是Y课程要求的X,或者您可以将所有要求建模为Y来自Y,其中单课程要求是“1比1”的要求。

我会推荐这样的东西:

Course
---------------
CourseID
Description
...

Program
---------------
ProgramID
Description
...

CourseGroup
---------------
CourseGroupID
CourseID

ProgramCourseGroup
---------------
ProgramID
CourseGroupID
RequiredCourses

CourseProgram是两个顶级表格。它们分别定义了所有课程和程序的简单列表,两者之间没有任何关系。

CourseGroup定义了一组课程。这涉及Course,但没有其他表格。

ProgramCourseGroup将课程组与课程联系起来。一个程序表明需要一组特定的课程,然后RequiredCourses表示必须从该组中选择多少课程才能满足要求。

例如,假设您有一个名为“篮子编织”的程序,需要:

  • 篮子简介
  • 基本编织技术

以下四门课程中的两门:

  • 复活节篮子
  • 手提篮
  • 野餐篮
  • SCUBA Diving

您的数据如下:

Course
------------------------------------
CourseID    Description
1           Intro to baskets
2           Basic weaving techniques
3           Easter baskets
4           Handbaskets
5           Picnic baskets
6           SCUBA Diving

Program
--------------------------
ProgramID   Description
1           Basket Weaving

CourseGroup
--------------------------
CourseGroupID  CourseID
1              1
2              2
3              3
3              4
3              5
3              6

ProgramCourseGroup
-----------------------------------------
ProgramID  CourseGroupID  RequiredCourses
1          1              1
1          2              1
1          3              2

答案 3 :(得分:2)

原则上,人们希望能够创建类似这样的约束来强制执行规则:

CHECK
 (NOT EXISTS
  (SELECT 1
   FROM CourseEnrolement c, ProgramEnrolement p
   WHERE c.StudentId = p.StudentId
   AND c.ProgramId = p.ProgramId
   GROUP BY p.StudentId, p.ProgramId, p.NumberOfCoursesRequired
   HAVING COUNT(*) <> p.NumberOfCoursesRequired
 ))

不幸的是,SQL几乎不可能实现这一点,或者至少在强制执行约束时更新数据库非常困难。因此,如果您真的想在数据库中表示这样的规则,那么您可能需要一个比SQL更好的模型。实际上,这些规则通常会在应用程序代码中强制执行。

答案 4 :(得分:1)

您可以将这些课程视为“集合”,然后使用单独的COURSES_SET表格来显示集合的ID。您的课程可以有一个参考课程表的optinal set_id。这只是一种方式来做到这一点......

示例:


COURSES_SET

CourseSetID      Name
-----------      ---------------
1                Early Renaissance medical techniques
2                Jurassic theological certificate program
3                Mad Science

COURSES

CourseID     Name                   CourseSetID          CourseSequenceNumber
--------     --------------------   -----------          ---------------------
1001         The joys of leeches    1                    1

2011         How to keep your
             patient from dying     1                    2

1700         Is there a T-Rex?
             Arguements for and
             Against                2                    1

1301         Intro to Algorithms    (NULL)               (NULL)

3301         Cackling: An advanced  3                    3
             course

此模型允许您将课程与课程相关联(尽管您也可以称之为“课程”)课程,并指定序列号以确保学生以正确的顺序学习。如果两个课程可以同时进行(两个课程之间的顺序并不重要),您可以为它们分配相同的序列号。

然后你可以有一个由STUDY_PROGRAM引用的单独的COURSES_SET表,这样你就可以知道一组课程属于哪个学习计划。

答案 5 :(得分:1)

|--------|         |-----------------|             |---------------|
|Program |         |CoursesByProgram |             |Courses        |
|------  |         |-----------------|             |---------------|
|Id PK   |---------|ProgramId   PK   |             |CourseId    PK |
|Name    |         |CourseId    PK   |-------------|Name           |
|--------|         |ListId FK NULL   |             |---------------|
                   |IsCompulsory  bit|             
                   |IsElective    bit|             
                   -------------------
                          |
                          |
                  |-----------------|
                  |List             |
                  |-----------------|
                  |Id        PK     |
                  |Name             |
                  ------------------

答案 6 :(得分:1)

我会做以下事情:

首先创建课程表:

courseId  courseName IsCompulsory OptionalCoursesId
========  ========== ============ ================

然后有一个可选课程的第二个表:

OptionalcourseId courseName etc etc etc
================ ========== === === ===

本质上,第二个表是包含所有可选选项的链接表。

示例:

courseId  courseName IsCompulsory OptionalCoursesId
========  ========== ============ ================
1         science    0            NULL
2         IT         0            2
在OptionalCourses表中:

OptionalcourseId courseName 
================ ========== 
2                SQL       
2                C#
2                Java
5                Extreme Ironing
5                Native Julu dancing

希望这是有道理的

答案 7 :(得分:1)

我会定义一个表Courses,一个表Components,一个表Programmes以及CoursesComponents之间以及Components之间的链接表}和ProgrammesProgrammesComponents之间的链接表还应该有一个列,指示链接到的程序需要多少来自链接组件的信用。

例如,您可以拥有一个数学组件,在该组件中您可以使用代数,三角函数,微积分I和II等课程 - 这可以通过ComponentCourses表中的记录来表示。
然后,您可以同时拥有需要数学课程的Physics程序和Chemistry程序。如果Physics要求6学分数学而Chemistry需要4学分,则表示Credits表中的ProgrammeComponents值。

要了解某一系列课程是否符合课程的文凭要求,只需将课程的学分值与每个部分相加,然后查看它是否与ProgrammeComponents表中所需的值相符。

实施例

下面是一些示例表数据,它定义了两个程序,物理和化学,以及它们与三个数学课程和一个力学课程的关系。学生的要求如下:

  • 物理系学生必须从4种可用中选择至少3个数学学分。
  • 化学专业的学生必须选择至少2个数学学分
  • 物理系学生可以选择学习技巧。

如您所见,此架构非常灵活,只要确定您可以“挑选”最少量(0 <组件中所需的信用额&lt;组件总积分)的课程“篮子”,完全选修课程(必修学分= 0)以及必修课程(必修学分=成分总学分)。

架构:

Courses                                Components
*******                                **********
CourseId | CourseName   | Credits      ComponentId | ComponentName
1        | Algebra      | 1            1           | Math
2        | AP Algebra   | 2            2           | Physics
3        | Trigonometry | 1
4        | Mechanics    | 1

ComponentCourses                Programmes
****************                **********
CourseId | ComponentId          ProgramId | ProgramName
1        | 1                    1         | Physics
2        | 1                    2         | Chemistry
3        | 1
4        | 2

ProgrammeComponents
ProgrammeId | ComponentId | RequiredCredits
1           | 1           | 3
2           | 1           | 2
1           | 2           | 0