改进在线考试的数据库设计

Improve database design for online exam

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

要求:

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

我的设计:

tables:

学生
stud_id、姓名、电子邮件

测试
test_id、测试名称、持续时间

问题
que_id,问题,opt1,opt2,opt3,opt4,答案,test_id

答案
stud_id, que_id, 回答

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

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

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

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

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

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

的答案

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

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

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

这是测试 table:

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

我们通过 table.

将学生与测试联系起来
StudentTest
-----------
Student ID
Test ID
Test Started Timestamp
Test Duration
...

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

问题table。

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

答案table。

Answer
------
Answer ID
Answer Text

现在事情变得棘手了。您可以根据 ID 将问题分配给测试,如下所示。

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

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

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

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

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

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

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

并将问题和答案复制到历史tables.

出于类似的原因,我们创建 QuestionAnswer table 如下:

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

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

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

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

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

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

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

初始响应

了解数据

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

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

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

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

  1. 你的answerstable没有诚信。就目前而言,可以针对未询问学生的问题或学生未参加的测试记录答案。防止此类错误在关系数据库中很常见,而在记录归档系统中是不可能的。
  • 在 IT 的这些黑暗日子里,这是一个普遍的趋势。人们关注数据价值;他们想象电子表格形式的 ,然后他们直接实施包含那些 的 object。而不是理解 数据 及其含义。

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

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

问题

By this way answers can be stored but it increase the number of records as for every question solved by student new record will be added in answers table.

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

Is there any better way to do this where number of records will be less.

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

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

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

e.g. One test consists 100 questions and 1000 students take that test, for every student there will be 100 records for each question and for 1000 students 100k records.

是的。但这是用 ISAM record-processing 术语表示的。在数据库方面,您无法回避数据库存储的事实:

  • 关于 100 个问题的事实

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

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

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

此外,SQL 平台,在某种程度上 non-SQL 平台,例如 MySQL,都针对处理数据集进行了高度优化(而非 record-by-record) ;重 I/O 和缓存;等等,如果你实现高并发所需的结构,你将获得更多的性能。

实施

就实施而言,特别是因为您关注性能,所以存在错误。重申一下,在理解数据并正确建模之前,不应尝试实施。

全面的问题是,您添加了一个代理项(没有“代理键”这样的东西,它只是一个代理项,一个物理记录 ID)。在建模练习中还为时过早;进展还不够;模型不是 stable,要添加代理。

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

  • 代理项不提供关系数据库中要求的行唯一性

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

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

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

解决方案

不折腾了,我提供方案,你们可以讨论。

  • Student Test Data Model(仅第 1 页,适用于后续进度)。

  • 如果您不习惯使用符号,请注意每一个小刻度、刻痕和标记,实线与虚线,方角与圆角,都表示非常具体的含义。参考IDEF1X Notation.

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

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

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

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

Your answers table has no integrity. As it stands, answers can be recorded against a question that the student was not asked, or for a test that the student did not sit. Prevention of that type of error is ordinary fare in a Relational Database, and it is not possible in a Record Filing System.

  • 那现在被阻止了。现在名为 student_response,answers table 现在具有一定的完整性。 studentstudent_test, 中注册了一个测试,student_responses 被限制为 student_test.

请comment/discuss.

回复评论

I will add additional table subject (subject_id, subject_name) and add that subject_id in question table as FK is this okay?

当然可以。但这会产生后果。确保我们全面正确地做到这一点的一些建议:

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

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

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

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

  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 table,我认为我们希望 students 被注册对于 subjects,,因此 student_test 被限制为 subjectsstudent 已注册。

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

      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. 现在数据模型似乎已经完成。

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

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

  • 请注意,child table 中的关系键为 parent table 提供关系完整性,到层次结构中的每个更高级别(parent、grandparent)。

  • 如果不是很明显,请注意关系连接的强大功能。每个文件中都有 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