【问题标题】:How to store multiple options in a single table?如何在一个表中存储多个选项?
【发布时间】:2015-12-13 16:57:52
【问题描述】:

我想设计一个用于结果计算的应用程序。

首先,我需要知道如何在 MySQL 数据库中存储记录,以便学生可以拥有尽可能多的课程,例如学生 A 可以附加 6 个科目,而学生 B 可以附加 12 个科目。

在这种情况下,我需要知道如何设计一个数据库结构,以允许字段以 数组 的形式存储尽可能多的主题。

我们将不胜感激任何建议或更好的处理方法。

【问题讨论】:

  • 不,你不想那样做。我或其他人会告诉你

标签: mysql database-design


【解决方案1】:

为了完整起见,这不是一般推荐的解决方案:

5.7.8 版本开始的MySQL 提供JSON datatype,它允许以JSON 格式存储和检索对象和数组。

这样,您可以将整个对象和数组存储到一个字段中,就像数组的样子:

 ['subject_1', 'subject_2', 'subject_3']

尤其是初学者不知道这一点,他们通过另一种逗号分隔的字符串实现或使用与语言相关的序列化/反序列化方法重新发明轮子。

至少 JSON 作为一种数据交换格式是非常常用且易于解析的。

在 MySQL 字段中使用存储数组和对象有一些有效的用例,例如用于速度优化或当您有未知或动态属性仍想保存在数据库中时。

但根据经验,如果您依赖将对象和数组存储到 MySQL 中,那么您的数据库设计很可能会被破坏,或者您应该使用基于文档的数据库,例如 MongoDB

【讨论】:

    【解决方案2】:

    请阅读Data NormalizationGeneral Indexing 概念和Foreign Key 约束,以保持数据清洁和参照完整性。这会让你继续前进。

    在纸面上将数据存储在数组中对您来说似乎很自然,但对于 db 引擎而言,其性能主要是不使用索引。此外,您会在第 2 天发现,获取和维护您的数据将是一场噩梦。

    以下内容应该能让您在修补时有一个良好的开端。 Joins 也是。

    create table student
    (   studentId int auto_increment primary key,
        fullName varchar(100) not null
        -- etc
    );
    
    create table dept
    (   deptId int auto_increment primary key,
        deptName varchar(100) not null -- Economics
        -- etc
    );
    
    create table course
    (   courseId int auto_increment primary key,
        deptId int not null,
        courseName varchar(100) not null,
        -- etc
        CONSTRAINT fk_crs_dept FOREIGN KEY (deptId) REFERENCES dept(deptId)
    );
    
    create table SCJunction
    (   -- Student/Course Junction table (a.k.a Student is taking the course)
        -- also holds the attendance and grade
        id int auto_increment primary key,
        studentId int not null,
        courseId int not null,
        term int not null, -- term (I am using 100 in below examples for this term)
        attendance int not null, -- whatever you want, 100=always there, 0=he must have been partying,
        grade int not null, -- just an idea   
        -- See (Note Composite Index) at bottom concerning next two lines.
        unique key(studentId,courseId,term), -- no duplicates allowed for the combo (note student can re-take it next term)
        key (courseId,studentId),
        CONSTRAINT fk_sc_student FOREIGN KEY (studentId) REFERENCES student(studentId),
        CONSTRAINT fk_sc_courses FOREIGN KEY (courseId) REFERENCES course(courseId)
    );
    

    创建测试数据

    insert student(fullName) values ('Henry Carthage'),('Kim Billings'),('Shy Guy'); -- id's 1,2,3
    insert student(fullName) values ('Shy Guy');
    
    insert dept(deptName) values ('History'),('Math'),('English'); -- id's 1,2,3
    
    insert course(deptId,courseName) values (1,'Early Roman Empire'),(1,'Italian Nation States'); -- id's 1 and 2 (History dept)
    insert course(deptId,courseName) values (2,'Calculus 1'),(2,'Linear Algebra A'); -- id's 3 and 4 (Math dept)
    insert course(deptId,courseName) values (3,'World of Chaucer'); -- id 5 (English dept)
    
    -- show why FK constraints are important based on data at the moment
    insert course(deptId,courseName) values (66,'Fly Fishing 101'); -- will generate error 1452. That dept 66 does not exist
    -- That error is a good error to have. Better than faulty data
    
    -- Have Kim (studentId=2) enrolled in a few courses
    insert SCJunction(studentId,courseId,term,attendance,grade) values (2,1,100,-1,-1); -- Early Roman Empire, term 100 (made up), unknown attendance/grade
    insert SCJunction(studentId,courseId,term,attendance,grade) values (2,4,100,-1,-1); -- Linear Algebra A
    insert SCJunction(studentId,courseId,term,attendance,grade) values (2,5,100,-1,-1); -- World of Chaucer
    
    -- Have Shy Guy (studentId=3) enrolled in one course only. He is shy
    insert SCJunction(studentId,courseId,term,attendance,grade) values (3,5,100,-1,-1); -- Early Roman Empire, term 100 (made up), unknow attendance/grade
    -- note if you run that line again, the Error 1062 Duplicate entry happens. Can't take same course more than once per term
    

    一些简单的问题。

    哪个部门有什么课程?

    全部显示,使用表格别名(缩写)来减少打字次数,提高可读性(有时)

    select c.courseId,c.courseName,d.deptId,d.deptName
    from course c
    join dept d
    on c.deptId=d.deptId
    order by d.deptName,c.courseName -- note the order
    +----------+-----------------------+--------+----------+
    | courseId | courseName            | deptId | deptName |
    +----------+-----------------------+--------+----------+
    |        5 | World of Chaucer      |      3 | English  |
    |        1 | Early Roman Empire    |      1 | History  |
    |        2 | Italian Nation States |      1 | History  |
    |        3 | Calculus 1            |      2 | Math     |
    |        4 | Linear Algebra A      |      2 | Math     |
    +----------+-----------------------+--------+----------+
    

    谁在这个学期参加乔叟世界课程?

    (知道 courseId=5)

    以下内容受益于我们在 SCJunction 中的综合索引之一。复合是多列上的索引。

    select s.StudentId,s.FullName
    from SCJunction j
    join student s
    on j.studentId=s.studentId
    where j.courseId=5 and j.term=100
    +-----------+--------------+
    | StudentId | FullName     |
    +-----------+--------------+
    |         2 | Kim Billings |
    |         3 | Shy Guy      |
    +-----------+--------------+
    

    Kim Billings 就读于这个学期的哪个学期?

    select s.StudentId,s.FullName,c.courseId,c.courseName
    from SCJunction j
    join student s
    on j.studentId=s.studentId
    join course c
    on j.courseId=c.courseId
    where s.studentId=2 and j.term=100
    order by c.courseId DESC -- descending, just for the fun of it
    +-----------+--------------+----------+--------------------+
    | StudentId | FullName     | courseId | courseName         |
    +-----------+--------------+----------+--------------------+
    |         2 | Kim Billings |        5 | World of Chaucer   |
    |         2 | Kim Billings |        4 | Linear Algebra A   |
    |         2 | Kim Billings |        1 | Early Roman Empire |
    +-----------+--------------+----------+--------------------+
    

    Kim 不知所措,所以放弃数学课

    delete from SCJunction
    where studentId=2 and courseId=4 and term=100
    

    运行上面的选择语句,显示 Kim 正在服用什么:

    +-----------+--------------+----------+--------------------+
    | StudentId | FullName     | courseId | courseName         |
    +-----------+--------------+----------+--------------------+
    |         2 | Kim Billings |        5 | World of Chaucer   |
    |         2 | Kim Billings |        1 | Early Roman Empire |
    +-----------+--------------+----------+--------------------+
    

    啊,更简单的术语。不过爸爸不会高兴的。

    注意诸如 SCJunction.term 之类的东西。关于这一点可以写很多,我现在主要跳过它,除了说它也应该在某个 FK 中。您可能希望您的术语看起来更像 SPRING2015 而不是 int。

    就 id 而言。这就是我会做的方式。这是个人喜好。这将需要知道 id #'s,查找它们。其他人可以选择使用类似 HIST101 而不是 17 的 courseId。它们的可读性更高(但索引速度较慢(几乎没有)。所以做最适合你的吧。

    注意综合指数

    复合索引(INDEX 表示 KEY,反之亦然)是一种组合多个列以实现快速数据检索的索引。 SCJunction 表中两个组合的顺序被翻转,因此,根据查询数据的范围,数据库引擎可以根据您要处理的最左侧列选择用于最快检索的索引.

    至于唯一键#1,它旁边的注释说明不强制重复(意思是垃圾数据)是不言自明的。例如,学生 1 course 1 term 1 不能在该表中存在两次。

    要理解的一个关键概念是left-most 索引中列名排序的概念。

    对于studentId only 之后的查询,则使用首先列出studentId 的键(left-most)。在courseId only 之后的查询中,使用最左边具有courseId 的键。在同时关注 studentId 和 courseId 的查询中,数据库引擎可以决定使用哪个组合键。

    当我说“继续”时,我的意思是在 on clausewhere clause 条件下。

    如果没有这两个复合键(其中的第 1 列和第 2 列被翻转),那么在所查找的列不是 left-most 索引的查询中,您将不会从键的使用中受益,并且会受到缓慢的影响表扫描数据返回。

    所以,这两个索引结合了以下两个概念

    • 基于最左侧或两者(studentId 和 courseId 列)的快速数据检索
    • 根据 studentId、courseId 和学期值强制该表中的数据不重复

    外卖

    重要的要点是,Junction 表可以实现快速索引检索,以及对数据的合理管理与以逗号分隔的数据(数组思维方式)塞进一列,以及使用这种构造。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-03
      • 1970-01-01
      • 2011-12-25
      • 2022-11-04
      • 2019-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多