【问题标题】:Improve database design for online exam改进在线考试的数据库设计
【发布时间】:2015-07-08 21:12:11
【问题描述】:

我正在做一个多项选择在线测试项目,我设计了数据库来存储结果,但正在寻找更优化的方式。

要求:

  1. 每个问题都有四个选项。
  2. 只能选择一个选项,并且需要存储在数据库中。

我的设计:

表格:

学生
stud_id、姓名、电子邮件

测试
test_id、testname、持续时间

问题
que_id, question, opt1, opt2, opt3, opt4, answer, test_id

答案
stud_id,que_id,回答

通过这种方式可以存储答案,但它会增加记录数,因为学生解决的每个问题都会在答案表中添加新记录。

例如 一项测试包含 100 个问题,1000 名学生参加该测试,每个学生的每个问题将有 100 条记录,而 1000 名学生将有 100k 条记录。

有没有更好的方法来减少记录数。

【问题讨论】:

  • 我会有一个单独的答案表,然后一个问题可以有一个外键到答案表中的值。这将允许您为每个问题生成随机答案,包括正确答案。
  • 有 100k 个答案。如果需要存储,没有别的办法……

标签: mysql database database-design


【解决方案1】:

初始响应

了解数据

你做得很好。就数据而言,设计是正确的,但不完整。有两个错误:

  1. opt1…opt4 是一个重复组,它打破了 2NF。它必须放在单独的表格中。
  • 此外,似乎没有选项名称或描述符,这很奇怪(您在页面上绘制了什么,在每个单选按钮旁边?)

  • 如果您添加了第五个选项,现在可以满足;如果您有少于四个选项的问题,现在可以解决。

  • 相反,您有一组固定的列,如果将来有任何此类更改,您必须同时更改数据库和现有代码。而且代码会很可怕(额外处理而不是直接选择)

  1. 您的answers 表没有完整性。就目前而言,可以针对学生未被问到的问题或学生没有参加的测试记录答案。防止这种类型的错误是关系数据库中的普通费用,而在记录归档系统中是不可能的。
  • 在 IT 的这些黑暗日子里,这是一个普遍的趋势。人们关注数据价值;他们以电子表格的形式想象,然后他们直接去实现包含这些的对象。而不是理解数据及其含义。

  • answers(stud_id, que_id, answer) 没有意义,没有完整性,除非声明了 student_test 的上下文。

  1. 第三项不是错误,因为您没有将其作为要求提供。但是,在我看来,一个问题可以用于多个测试。按照您设置的方式,此类问题将被重复(数据库的全部意义在于对其进行规范化,以确保没有重复)。
  • 当然,结果是一个关联表,test_question.

问题

通过这种方式可以存储答案,但它会增加记录的数量,因为学生解决的每个问题都会在答案表中添加新记录。

是的。这对于数据库来说是正常的。

有没有更好的方法来减少记录数。

对于记录归档系统,是的。对于数据库,没有。由于您已将您的问题标记为数据库设计,我会假设这就是您想要的。

数据库是事实的集合,而不是具有相关字段的记录。事实是关于现实世界的,仅限于数据库和应用程序的范围。

确定我们需要的离散事实很重要,因为从属事实依赖于高阶事实。那就是数据库设计。随着我们的进步,我们将数据标准化,作为同一个练习的一部分。规范化的目的是消除重复,否则就会出现更新异常。我们确定关系键,随着我们的进步,再次作为同一个练习的一部分。关系键提供关系数据库的逻辑结构,即。逻辑完整性。

例如一项测试包含 100 个问题,1000 名学生参加该测试,每个学生的每个问题将有 100 条记录,而 1000 名学生将有 100k 条记录。

是的。但这是在 ISAM 记录处理术语中表达的。在数据库方面,您无法绕过数据库存储的事实:

  • 关于 100 个问题的事实

  • 关于 1,000 名学生的事实

  • 关于 1,000 名学生的事实乘以他们做出的 100 个选择

您需要了解两件事:大量离散的事实;以及复合键的使用。两者对于关系数据库都是必不可少的。如果缺少其中任何一个,或者您不情愿地实施它们,您将无法获得关系数据库的完整性、功能或速度,您将拥有 1970 年前的 ISAM 记录归档系统。

此外,SQL 平台,以及某种程度上的非 SQL 平台,例如 MySQL,都针对处理数据集(不是逐条记录)进行了大量优化;繁重的 I/O 和缓存;等等。如果你实现了高并发所需的结构,你将获得更高的性能。

实施

就实现而言,特别是由于您关心性能,因此存在错误。重申一下,在正确理解和建模数据之前,不应尝试实施。

全面的问题是您添加了一个代理(没有“代理键”之类的东西,它只是一个代理,一个物理记录ID)。建模工作还为时过早;进展不够;模型不稳定,添加代理。

  • 代理项始终是附加列加上基础索引。显然这会消耗资源,并且在插入和删除方面有成本。

  • 代理不提供行唯一性,这在关系数据库中是必需的。

  • 关系模型要求键由数据组成。关系键提供行唯一性。

  • 代理不是由数据组成的。因此,它不是一个关系键,它不提供任何一个特性。

  • 如果使用代理,它不会替换 Key,它是除了 Key。这就是为什么我们在对数据建模之后而不是之前评估对代理的需求。这是一个实现问题,而不是建模问题。

解决方案

与其来来回回,不如让我提供方案,你们可以讨论。

  • Student Test Data Model(仅限第 1 页,适用于跟随进度的人)。

  • 如果您不习惯符号,请注意,每一个小勾号、缺口和标记,实线与虚线,方角与圆角,都意味着非常具体的东西。参考IDEF1X Notation

  • 对于testquestion.,我保留了id 列,但请注意,使用简短而有意义的代码会更好。

  • student_id 有效,因为nameemail 都太大而无法迁移到子表。

  • 请仔细检查动词短语,它们包含一组谓词。谓词的其余部分可以直接从模型中确定。如果不清楚,请询问。

  • 看看你是否可以确定这是事实的集合,并且每个事实都是离散的,因为其他事实依赖于它;它不是具有相关字段的记录集合。

您的answers 表没有完整性。就目前而言,可以针对学生未被问到的问题或学生没有参加的测试记录答案。防止这种类型的错误是关系数据库中的普通费用,而在记录归档系统中是不可能的。

  • 现在已阻止。 answers 表,现在命名为 student_response,,现在具有一定的完整性。 studentstudent_test, 中注册用于测试,student_responses 被限制为 student_test.

请评论/讨论。

回复评论

我将添加额外的表主题 (subject_id, subject_name) 并将该主题表添加到问题表中,因为 FK 可以吗?

是的,无论如何。但这有后果。一些建议,以确保我们全面正确地做到这一点:

  1. 如上所述,除非绝对必须,否则不要使用代理项(记录 ID)。对于用户和开发人员而言,短代码更适合标识符
  • 如果您想了解与ID 列相关的问题的更多信息,请阅读this Answer
  1. 主题很重要。它是 (a) question 存在和 (b) test 存在的上下文。它们确实作为独立项目存在(DM 的第 1 页),但现在它们从属于subject.,添加大大提高了数据完整性。

  2. 学生注册的事实和学生参加考试的事实是离散和独立的事实。

  3. 谢天谢地,这消除了两个代理 question_idtest_id. Short codesCHAR(2) 更容易和更有意义。

  4. 注意表名的改进,提高了清晰度。

  5. 我已经更新了Student Test Data Model(仅第 2 页,适用于那些关注进度的人)。

  6. 但是,这会暴露一些东西(这就是我们对数据建模的原因,纸张很便宜,许多草稿都是正常的)。如果我们评估谓词(在数据模型中很容易看到,详见IDEF1X Notation 文档):

       each subject_test was taken by 0-to-n student_tests
       each student_test is [a taking of] 1 subject_test
       each student took 0-to-n student_tests
       each student_test is taken by 1 student
    

那些谓词不准确。 student 可以在任何 subject. 中代表 test 鉴于新的 subject 表,我认为我们希望将 students 注册为 subjects,,因此将 student_test 限制为 @ student 注册的 987654361@。

  • 如果您想了解有关谓词的重要关系概念以及如何使用它来理解和验证模型的信息,请访问 this Answer,向下滚动直到找到 谓词 部分,并仔细阅读。
  1. 我已经更新了Student Test Data Model(第3页)。现在我们有了更多的完整性,因此student_test 被限制为student 注册的subjects。相关的谓词是:

      each student registered for 0-to-n student_subjects
      each student_subject is a registration of 1 student
      each subject attracted 0-to-n student_subjects
      each student_subject is an attraction of 1 subject
    
      each subject_test was taken by 0-to-n student_tests
      each student_test is [a taking of] 1 subject_test
      each student_subject took 0-to-n student_tests
      each student_test is taken by 1 student_subjects
    
  2. 现在数据模型似乎已经完成了。

  • 上下文是数据库中的一切。

  • 数据层次结构在键的复合中清晰可见。

  • 请注意,是子表中的关系键为层次结构中的每个更高级别(父、祖父)提供与父表的关系完整性。

  • 如果不是很明显,请注意关系连接的强大功能。对于每个文件中都有 ID 字段的记录归档系统,您无法做到这一点。例如:

      - Join `student_response` directly to `subject` on `subject_code`, without having to navigate the two levels in-between
    
      - Join `student_response` directly to `student` on `student_id`, without having to navigate the two levels in-between
    

【讨论】:

  • 看的挺清楚的。感谢您的努力和详细的回答,这真的很有帮助。我将添加额外的表主题(subject_id,subject_name)并将该subject_id添加到问题表中,因为FK可以吗?
  • 1. opt1…opt4 是一个重复组,是的,我将为选项添加新表。 2. 我有一张作为taste_taken 的表,因为我没有添加任何问题。 3. 是的,有可能在不止一项测试中使用一个问题,并且采用新的规范化设计,易于管理。再次感谢您。
  • @kuldeep。不客气。需要详细答案的新主题表。我已经 (a) 在答案的底部添加了几个段落 (b) 我已经回复了一个新部分,详细回复了您的 cmets,并更新了 DM。
  • @Thorsten Kettner。 (a) 如果您想更好地了解 2NF,请打开一个新问题并联系我。或打开聊天会话。 (b) Surrogates 如果你研究我对 Kuldeep 的 cmets 的反应,它构成了模型的进展(第 2 页和第 3 页),你可能会注意到 question_id 已经消失了。之前的模型(第 1 页)确实为时过早分配代理。在开始时使用代理会削弱建模练习。
  • @PerformanceDBA 关注了您的链接答案,这些答案清除了很多问题,并感谢您提供更新的答案。
【解决方案2】:

不,没有更好的设计,因为设计与表中有多少记录无关。无论是处理十个学生还是一万个学生,你都会选择相同的设计。

您的餐桌设计看起来不错。不要担心记录的数量。一个 dbms 是用来处理大表的。 100k 条记录仍然是一个小型数据库。如果要存储数十亿个答案,我什至不会更改此设计。

【讨论】:

  • 我不太关心性能,但是你说的是对的,我将使用相同的设计和标准化。谢谢。
【解决方案3】:

如果你想规范化数据,那么我会创建一些不同的表。

您的学生表看起来不错。通常,我对表格使用单数名称,而不是复数。

Student
-------
Student ID
Name
Email
...

这是测试表:

Test
----
Test ID
Test Name
...

我们使用连接表将学生与测试联系起来。

StudentTest
-----------
Student ID
Test ID
Test Started Timestamp
Test Duration
...

测试的时间和长度因学生而异,因此这些列包含在 StudentTest 表中。

问题表。

Question
--------
Question ID
Question Text

还有答案表。

Answer
------
Answer ID
Answer Text

现在事情变得棘手了。您可以像这样根据 ID 将问题分配给测试。

TestQuestion
------------
Test ID
Question ID

但是,如果您这样做,并且有人在测试后更改了问题文本,那么问题 ID 指向的问题与测试中的问题不同。

为了解决这个问题,我们创建这样的历史表:

QuestionHistory
---------------
QuestionHistory ID
Question Text

AnswerHistory
-------------
AnswerHistory ID
Answer Text

所以,我们这样创建 TestQuestion 表:

TestQuestion
------------
Test ID
QuestionHistory ID

然后将问题和答案复制到历史表格中。

出于类似的原因,我们这样创建 QuestionAnswer 表:

QuestionAnswer
--------------
QuestionHistory ID
AnswerHistory ID
Is Correct Answer

您的代码可以确保每个问题都有 4 个可能的答案。该数据库允许多于或少于 4 个可能的答案。

最后,我们将学生的答案与试题联系起来。

StudentQuestionAnswer
---------------------
Student ID
Test ID
QuestionHistory ID
AnswerHistory ID
Is Correct Answer

是的,此处重复了测试 ID 列。这样您就可以按考试以及参加考试的学生进行查询。

Is Correct Answer 字段在 QuestionAnswer 表和 StudentQuestionAnswer 表中具有不同的含义。在 QuestionAnswer 表中,Is Correct Answer 布尔值指向正确答案。在 StudentQuestionAnswer 表中,Is Correct Answer 布尔值表示学生正确回答了问题。

这应该是一个完整的问题/答案数据库。如果需要,您可以将测试与课程联系起来。

【讨论】:

  • 是的,我将使用标准化。感谢您的时间和努力,这对我真的很有帮助。
【解决方案4】:

您可以将答案的详细信息存储为相应问题 ID 的 ~ 分隔记录,该记录也是 ~ 分隔的。这样一个学生证就只有一个记录。您还可以解码特定问题 id 的答案

【讨论】:

    猜你喜欢
    • 2010-12-18
    • 2021-12-03
    • 2011-08-05
    • 2019-11-27
    • 1970-01-01
    • 2013-12-09
    • 2014-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多