数据库设计/规范结构需要包含AND、OR、可选元素及其关系。

7
我希望将大学课程的细节存储在(MySql)数据库中,但我不确定如何维护模块和选择之间的关系。
基本上,一个课程可以有强制性部分、可选模块组、选项部分,而每个部分中都可以包含模块之间的ANDs或ORs关系。
简单示例: 一个60学分的课程有一些强制性模块,占40学分。剩下20学分可以从可选模块组中选择。(模块本身可以拥有不同的学分)。有效地说; ('强制性模块1' AND '强制性模块2'... AND '强制性模块N') AND (40学分来自'可选模块')
ANDs & ORs: 当我在上面提到模块时,它可以是单个模块,也可以是"模块x或模块y",即在强制性部分中。(这些模块显然必须具有相同的学分权重)。 或者在可选部分中可能会有单个模块,甚至其中一个选择可能是"模块x和模块y"。
选项: 学生可能需要修读强制性模块以及n个选项之一,这些选项可能包含ANDs、ORs和强制性和可选部分;即"选项"具有整体课程模块选择的所有属性。选项部分将与其他部分(如强制性或可选)进行AND或OR操作;即强制性模块"加上以下选项之一"。有效地说,选项部分只是'选项1' OR '选项2'... OR '选项N'。
问题在于,当运算符可能是另一个AND/OR操作或单个模块时,我如何存储所有AND和OR关系,并跟踪每个选择允许的学分数量;例如:"从以下选择中选择20个学分:"(可选模块组)。

每个模块都构成一个学分吗? - Jared
是的。最低的是5个学分模块。我见过的最高的是15个学分模块,但可能会有更高权重的模块。 - Adam Lynch
你的实际代码在哪里?因为我在这里看到的只是面向对象的无用功。 - jcolebrand
2
如果答案能够有图表就太好了... - C. Ross
5个回答

3
一个非常简单的、初步的方法是只使用4个表:

TABLE Course 
( CourseId 
, Title 
, TotalCredits 
, ... other stuff
, PRIMARY KEY (CourseId)
) ;

TABLE Module 
( ModuleId 
, Description 
, Hours
, Credits
, ... other stuff
, PRIMARY KEY (ModuleId)
) ;

同时需要考虑这两个因素的组合:

TABLE Course_Module 
( CourseID                 --- for this course
, ModuleID                 --- this module is allowed (optional or mandatory)
, PRIMARY KEY (CourseID, ModuleId)
, FOREIGN KEY (CourseId) 
    REFERENCES Course (CourseId)
, FOREIGN KEY (ModuleId)
    REFERENCES Module (ModuleId)
) ;

TABLE Course_MandatoryModule 
( CourseID                  --- for this course
, ModuleID                  --- this module is mandatory
, PRIMARY KEY (CourseID, ModuleId)
, FOREIGN KEY (CourseID, ModuleId)
    REFERENCES Course_Module (CourseID, ModuleId)
) ;

现在,如果你的模块和课程的允许组合更加复杂,正如你的描述所示,那么你可以定义一个复杂的分层模型,而不是使用 Course_ModuleCourse_MandatoryModule 表:

课程:

TABLE Course                        --- same as previous model
( CourseId 
, Title 
, TotalCredits 
, ... other stuff
, PRIMARY KEY (CourseId)
) ;

模块和(模块)组:

TABLE ModuleEntity                  --- the supertype for both
( ModuleEntityId                    --- modules and group of modules
, PRIMARY KEY (ModuleEntityId)
) ;

TABLE Module                        --- subtype
( ModuleId 
, Description 
, Hours
, Credits
, ... other stuff
, PRIMARY KEY (ModuleId)
, FOREIGN KEY (ModuleId) 
    REFERENCES ModuleEntity (ModuleEntityId)
) ;

TABLE ModuleGroup                  --- group of modules
( ModuleGroupId                    --- subtype of the supertype (entity)
, GroupDescription        
, PRIMARY KEY (ModuleGroupId)
, FOREIGN KEY (ModuleGroupId) 
    REFERENCES ModuleEntity (ModuleEntityId)
) ;

和关系(模块属于组):

TABLE Module_in_Group  
( ModuleEntityId               --- this module or group
, ModuleGroupId                --- is in this group
, PRIMARY KEY (ModuleEntityId, ModuleGroupID)
, FOREIGN KEY (ModuleEntityId)
    REFERENCES ModuleEntity (ModuleEntityId)
, FOREIGN KEY (ModuleGroupId)
    REFERENCES ModuleGroup (ModuleGroupId)
) ;

最后,课程可以拥有多个模块的组合:

TABLE Course_ModuleGroup
( CourseId                 --- for this course
, ModuleGroupId            --- this module group is allowed
, PRIMARY KEY (CourseID, ModuleGroupId)
, FOREIGN KEY (CourseId) 
    REFERENCES Course (CourseId)
, FOREIGN KEY (ModuleGroupId)
    REFERENCES ModuleGroup (ModuleGroupId)
) ;

好的,但是我如何在一个组中存储另一个组?就我所看到的,对于每个级别,在您的解决方案中需要有更多的表格? - Adam Lynch
不需要,Module_in_Group表会处理这个问题。使用这个模型,你甚至可以在一个组内部再嵌套一个组。或者是一个循环的情况,比如组A在B中,组B在C中,组C在A中,显然你不想要这种情况! - ypercubeᵀᴹ
谢谢您的回复。在我接受之前,请问ModuleGroup中的GroupDescription是什么?它只是一个文本描述/名称的地方吗?如果是这样,那么如何处理不同类型的分组呢?例如,一种类型的组是我必须从组中选择一定数量的学分,另一种是我选择全部(AND),还有一种是我选择其中一个(OR和选项)。非常感谢您对此的回答! - Adam Lynch

1
您可以在此处创建递归表结构,其中选项引用其父选项。
然后,可以通过查询所有具有“null”父项的选项来识别“主”选项。
“and-or”关系可以通过单独的“option-set”表实现,其中的主键是一个“选项”。 option-set表中的null自我引用是定义课程选项的“根”点。从那一点开始,您将选择具有parent = root的option-set记录。这将是第一个“级别”的选项。有些是强制性的,有些不是。为了表达这一点,您将必须在选项集表上具有布尔属性作为标志。因此,每个选项集都是由较小的选项集定义的。当然,最终,一旦到达底部,您的选项集将在某个时候定义实际类。
我建议这可以更有效地模拟为JSON或XML,因为这些数据结构以更具表现力的方式支持层次结构。

1
设计相当直观,您只需要一个带有约束条件的递归“组”表。
Course
- ID
- Title
- Credits

Course_Group
- CourseID
- GroupID

Group
- ID
- GroupID
- Description
- AtLeastNSelections
- AtLeastNCredits

Group_Module
- GroupID
- ModuleID

Module
- ID
- Title
- Credits

一个示例结构将是:

Course: 1, "Math Major", 60
Group: 1, NULL, "Core Modules", 2, 40
Course_Group: 1, 1
    Group: 2, 1, "Required (5) Core Modules", 5, 25
    Course_Group: 1, 1
    Group_Module: (1, 1), (1, 2), (1, 3), (1, 4), (1, 5)
        Module: 1, "Calculus I", 5
        Module: 2, "Calculus II", 5
        Module: 3, "Calculus III", 5
        Module: 4, "Stats I", 5
        Module: 5, "Stats II", 5
    Group: 3, 1, "Required (3) Of (N) Modules", 3, 15
    Course_Group: 1, 3
    Group_Module: (3, 6), (3, 7), (3, 8), (3, 9), (3, 10)
        Module: 6, "Number Theory", 5
        Module: 7, "Bridge Adv. Math", 5
        Module: 8, "Calculus IV", 5
        Module: 9, "Stats III", 5
        Module: 10, "Finite Math", 5
Group: 4, NULL, "Secondary Modules", 1, 20
Course_Group: 1, 4
    Group: 5, 4, "Comp. Sci.", 2, 0
    Course_Group: 1, 5
    Group_Module: (5, 11), (5, 12), (5, 13), (5, 14), (5, 15), (5, 16)
        Module: 11, "Math in Hardware", 4
        Module: 12, "Math in Software", 4
        Module: 13, "Programming 101", 4
        Module: 14, "Algorithms 101", 4
        Module: 15, "Programming I", 5
        Module: 16, "Programming II", 5
    Group: 6, 4, "Physics", 0, 8
    Course_Group: 1, 6
    Group_Module: (6, 17), (6, 18), (6, 19), (6, 20)
        Module: 17, "Physics Mechanics", 4
        Module: 18, "Physics Thermodynamics", 4
        Module: 19, "Physics Magnetism", 5
        Module: 20, "Physics Theoretical", 5
    Group: 7, 4, "Gen. Ed.", 0, 0
    Course_Group: 1, 7
    Group_Module: (7, 21), (7, 22), (7, 23), (7, 24)
        Module: 21, "Business Writing", 3
        Module: 22, "Ethics", 3
        Module: 23, "Aesthetics", 3
        Module: 24, "Graphic Design", 3

快速浏览... 课程“数学专业”下有两个组,“核心模块”和“次要模块”。 “核心模块”需要至少2个子模块和至少40个学分。“次要模块”需要至少1个子模块和至少20个学分。

您可以看到,“核心模块”下的组的限制比“次要模块”下的组的限制更严格。

要输出上面的示例结构,可以使用以下代码:

SELECT c.Title, g.Description, m.Title FROM Course c
 INNER JOIN Course_Group cg ON c.ID = cg.CourseID
 INNER JOIN Group g ON cg.GroupID = g.ID
 INNER JOIN Group_Module gm ON g.ID = gm.GroupID
 INNER JOIN Module m ON gm.ModuleID = m.ID
WHERE c.ID = 1
ORDER BY g.GroupID, g.ID, m.Title  

如果您有一个课程和模块,您可以从Course_Group表中获取该课程的所有组,并从Group_Module表中获取模块所属的组。一旦您将模块放入它们的组中,您可以检查组的约束条件AtLeastNSelections和AtLeastNCredits,沿着Group.GroupID的父级链向上走,直到您到达Group.GroupID = NULL。


所以 Group.GroupID 引用了 Group.ID 是吧? - Adam Lynch
1
@Adam Lynch - 是的,我通常尝试将FK/引用保持为表+列的名称。 - Louis Ricci

1

你可以尝试像这样做:

TABLE course_definition (
    ID int,
    num_mandatory_sections int,
    mandatory_hours int,
    num_optional_modules int,
    optional_hours int,
);

TABLE modules (
    ID int,
    Description varchar(max),
    hours int,
    ....
);

TABLE course (
    Course_ID int FOREIGN KEY (course_definition.id),
    module_id int FOREIGN KEY (modules.id)
);

TABLE course_module_relationship (
     Course_ID int FOREIGN KEY (course_definition.id),
     module_ID int foreign key (modules.id),
     Requirement_flag ENUM ("MANDATORY", "OPTIONAL")
);

TABLE Student_to_course (
     Student_ID int,
     Course_ID int foreign key (course_definition.id)
);

TABLE Student_to_module (
     Student_ID int,
     Module_ID int FOREIGN KEY (module.id)
);

如果您真的需要能够创建组模块,即从多个其他模块创建的单个模块,则表module将需要具有标志字段:

group_module boolean

应添加以下表格:

TABLE module_groupings (
    group_module_ID int foreign key (module.id)
    dependant_module_id int foreign key (module.id)
);

这更像是伪代码,但你可以理解这个思路。表格coursecourse_module_relationship将没有键,并且存储您的关系,因为我了解到它们可能是多对多的。所以基本上读取处理选择的代码将不得不检查是否符合course_definition的标准。

如果强制性部分与课程之间是一对一的关系,则可以将强制性部分分成单独的表格,但您必须更彻底地分析数据。


但这可以递归吗?即选项就像整个模型一样,可以在其中具有强制性部分等。 - Adam Lynch
模块依赖于模块?我的理解是您基本上正在尝试在数据库中实现大学学位课程,其中课程或模块是您要构建的原子。但是,如果您需要一个先决条件表,那很容易做到。 - Karlson
学生可能需要修4个必修模块和3个选项中的1个(每个选项都有2个必修模块和一组模块,其中必须选择10个学分),以及从一组可选模块中获得20个学分。 - Adam Lynch
我不确定是否理解了要求,但如果您有单独的模块和组模块(一组单独的模块),那么我已经添加了描述它们之间关系的代码。这个想法是将数据分解为最基本的部分,然后有一个表来建立它们之间的关系。 - Karlson

0

一个生产质量的系统,使用和/或(像你的系统一样),你可以免费查看的是entlib 5.0安全块。http://entlib.codeplex.com/

每个规则都通过名称检索以获取完整表达式。模式和实践团队为表达式创建了自己的简短DSL,以避免使xml结构/数据库结构复杂化。

这在实践中的手动实验ex02 app.config中。要将规则存储在数据库中,您需要实现自定义AuthorizationRuleProvider。

R:= rolename; U:= username

  <securityConfiguration defaultAuthorizationInstance="RuleProvider"
defaultSecurityCacheInstance="">
<authorizationProviders>
  <add type="Microsoft.Practices.EnterpriseLibrary.Security.AuthorizationRuleProvider, Microsoft.Practices.EnterpriseLibrary.Security"
    name="RuleProvider">
    <rules>
      <add expression="R:Employee OR R:Developer OR R:Manager" name="Raise Bug" />
      <add expression="R:Manager" name="Assign Bug" />
      <add expression="R:Developer OR R:Manager" name="Resolve Bug" />
    </rules>
  </add>
</authorizationProviders>

开发者使用

    public static AssignBug Create()
    {
        // TODO: Check Authorization
        if (!SecurityHelper.Authorized(AuthRule.Assign))
        {
            throw new SecurityException();
        }

        return new AssignBug();
    }

虽然这不是一个直接的答案,但我认为它提供了一个很好的例子,展示了如何实现基于规则表达式的系统。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接