SQL CRUD¶
Create, Read, Update, and Delete (CRUD) are the four basic operations of persistent storage.
CRUD and SQL
| CRUD | SQL |
|---|---|
| Create | INSERT |
| Read | SELECT |
| Update | UPDATE |
| Delete | DELETE |
CREATE¶
通用格式:
| SQL | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
假设我们已经创建了上述Students Courses Association表,那么我们便可以使用 INSERT INTO 来向这些空表中插入数据:
| SQL | |
|---|---|
1 | |
也可以 指定字段名:
| SQL | |
|---|---|
1 2 | |
没有指定值得字段将默认被分配 NULL 或 DEFAULT 值,如果字段是 NOT NULL 属性,则会引发错误。
也可以 一次性插入多条记录:
| SQL | |
|---|---|
1 2 3 | |
| SQL | |
|---|---|
1 2 3 | |
Note
在 MySQL 中,TRUNCATE TABLE Students; 的作用是清空 Students 数据表中的所有数据,同时重置表的自增变量(AUTO_INCREMENT):
-
清空数据:
TRUNCATE TABLE会删除Students表中的所有记录,效果类似于DELETE FROM Students;,但不同的是TRUNCATE TABLE是一种更高效的操作,因为它不会一条一条地删除数据,而是直接清空整个表。 -
重置自增变量:如果
Students表中有使用自增(AUTO_INCREMENT)字段,比如主键id,TRUNCATE TABLE会将这个字段的计数器重置为初始值(通常是 1)。这样,插入新记录时,AUTO_INCREMENT字段会从 1 开始重新计数。
需要注意的是,TRUNCATE TABLE 操作无法被撤销(不可逆),一旦执行,数据将无法恢复。因此,在执行此操作前需要谨慎。
SELECT¶
基础操作¶
暴力地取出整张表
| SQL | |
|---|---|
1 | |
在生产环境中该操作应谨慎,因为若记录特别多或某个字段特别长,这将消耗大量的网络传输和内存消耗,且可能出现数据暴露等问题。
查看指定的列
| SQL | |
|---|---|
1 | |
WHERE¶
滤前聚合
我们可以使用 WHERE 进行筛选,例如查找所有开课时间为 2023-Spring 且学分大于 1 的课程:
| SQL | |
|---|---|
1 | |
IN
在使用 WHERE 进行过滤时我们可以指定 离散范围,如我们只想查询学分为 1 或 2 的课程:
| SQL | |
|---|---|
1 | |
IN的局限性
IN的查询是离散的,比如我想要查找学分在1~5之间的课程,只能写IN(1,2,3,4,5),太麻烦!
如果你想查询学分在 1 到 10 之间的课程,可以使==用 BETWEEN 运算符来指定一个范围==,而不是使用 IN。BETWEEN 可以用于查询一段 连续的范围。下面是如何实现这一点的 SQL 语句:
| SQL | |
|---|---|
1 | |
这条语句将查询 Courses 表中所有学分 (credit) 在 1 到 10 之间的记录,包括 1 和 10。
LIKE
可以使用 LIKE 进行模糊匹配,例如查找所有上课时间在 2022 年的课程:
| SQL | |
|---|---|
1 | |
更多例子可参见 SQL LIKE Operator (w3schools.com)
NULL
由前所述,没有被分配值且没有默认值得字段在插入时会被分配 NULL 这个空值来表示记录中的某个字段没有数据,可以使用 ISNULL 来进行判断。如找出所有没有登记分数得选课记录:
| SQL | |
|---|---|
1 | |
注意不能使用 grade = NULL 来判断是否为 NULL:
| SQL | |
|---|---|
1 2 | |
判断非空可以使用 NOT INSULL(<column_name>),如:
| SQL | |
|---|---|
1 | |
而不是
| SQL | |
|---|---|
1 2 | |
在SQL中,
!=的表示是<>
Note
- 判断是空:
ISNULL(<column_name>) - 判断非空:
NOT ISNULL(<column_name>)
COUNT, MAX, MIN, SUM, AVG¶
基础数学操作
可以查看 记录数:
| SQL | |
|---|---|
1 | |
类似的可以查看 最大值,最小值,合,平均值,例如计算所有课程得平均学分:
| SQL | |
|---|---|
1 | |
GROUP BY¶
过滤聚合
我们想看看在所有课程中 不同的等级分别有多少人:
| SQL | |
|---|---|
1 | |
在查询中,我们可以给字段、表、和查询赋予一个临时的,仅在这次查询中使用的名称。
例如对于上面的查询,我们给 COUNT(*) 一个 临时名称 student_count:
| SQL | |
|---|---|
1 | |
在 GROUP BY 时,你 通常不能选择没有受到 GROUP BY 修饰的字段,例如如下查询:
| SQL | |
|---|---|
1 2 | |
这是因为同一个 grade 可能包括多个 student_id,因此其定义不良好:
问题的本质
在 SQL 中,GROUP BY 是用来将查询结果按照某一列或多列的值进行分组,然后对每个分组执行聚合函数(例如 COUNT、SUM、AVG 等)。
例如:
| SQL | |
|---|---|
1 2 3 | |
这个查询会按照 grade 列进行分组,然后对每个 grade 计算学生的数量。
为什么报错?
让我们看一下这个报错的查询:
| SQL | |
|---|---|
1 2 3 | |
这个查询会报错,因为 student_id 字段没有用于 GROUP BY 中,也没有被任何聚合函数处理。
在分组操作中,SQL 必须明确地知道如何处理每一个字段。对于 grade 列,SQL 可以通过 GROUP BY grade 来理解,你希望按照 grade 分组。对于 COUNT(*),SQL 也明白你希望对每个 grade 组的记录进行计数。
但是,对于 student_id,问题出现了:因为 student_id 并没有被聚合,也没有在 GROUP BY 中定义,那么 SQL 该如何确定它在每个分组中的值呢?
例如,假设有多个学生的 grade 都是 A,但他们的 student_id 不同。SQL 不知道你想选择哪个 student_id,因为多个 student_id 可能对应同一个 grade。因此,查询没有明确的意义。
举个具体的例子
假设 Association 表的数据如下:
| student_id | course_id | grade |
|---|---|---|
| 1 | 101 | A |
| 2 | 102 | A |
| 3 | 103 | B |
| 4 | 104 | B |
| 5 | 105 | C |
当你写 SELECT student_id, grade, COUNT(*) FROM Association GROUP BY grade; 时,你要求 SQL 按照 grade 来分组并统计每个 grade 的记录数。
但是每个 grade 组内有多个 student_id,例如 grade 为 A 的组里有 student_id 为 1 和 2 的记录。
SQL 不知道应该在这个分组结果中展示 student_id 为 1 还是 2,因为你没有指定任何逻辑告诉 SQL 如何选择。所以,这个查询无法确定 student_id 应该展示哪个值,从而导致了报错。
投机取巧的解决方式
为了让 SQL 明确地知道如何处理 student_id,你可以:
- 使用聚合函数: 如果你只关心任意一个
student_id,可以使用聚合函数如MIN()或MAX()来选择一个。例如:这里SQL 1 2 3
SELECT MIN(student_id), grade, COUNT(*) FROM Association GROUP BY grade;MIN(student_id)会选择每个grade组中的最小student_id。 - 不在
SELECT中选择未分组或未聚合的字段: 只选择GROUP BY中的列和聚合列,如:这样可以避免歧义。SQL 1 2 3
SELECT grade, COUNT(*) FROM Association GROUP BY grade; - 使用子查询或
JOIN: 如果你需要更多字段,可以先聚合,再通过JOIN的方式获取额外的信息。
真正的解决方式
当我们希望选择其他列时,可以考虑使用 JOIN 选项(见后文)。
HAVING¶
滤后聚合
可能想要对 聚合算子的结果 进行过滤,比如试图选出大于 20 个人的等级:
| SQL | |
|---|---|
1 2 | |
WHERE是不行的!它没法对聚合后的结果进行filter!
此时我们可以用 HAVING 来过滤聚合算子的结果:
| SQL | |
|---|---|
1 | |
执行过程
- 分组: SQL 先根据
grade对数据进行分组。 - 聚合: 然后计算每个
grade组的学生数量 (COUNT(*))。 - 过滤: 最后,HAVING 子句对聚合结果进行过滤,只保留学生数量大于 20 的组。
WHERE 和 HAVING 的区别
WHERE 子句
- 作用范围:
WHERE子句用于在数据分组和聚合之前过滤表中的行。 - 限制:
WHERE不能用于过滤聚合函数的结果,因为在执行WHERE时,聚合操作尚未发生。 - 例如:
SQL 1 2
SELECT * FROM Association WHERE grade = 'A'; -- 这条语句在分组和聚合之前就过滤出了所有 grade 为 'A' 的行。
HAVING 子句
- 作用范围:
HAVING子句用于在数据分组和聚合之后过滤分组的结果。 - 功能:
HAVING允许你对聚合函数的结果进行过滤。
示例
假设你有如下的 Association 表:
| grade | student_id |
|---|---|
| A | 1 |
| A | 2 |
| A | 3 |
| B | 4 |
| B | 5 |
| C | 6 |
如果你想要统计每个 grade 的学生数量,并且只选择学生数量大于 2 的 grade,你不能使用 WHERE,因为 WHERE 子句无法访问聚合后的结果。
错误示例(使用 WHERE 过滤聚合结果):
| SQL | |
|---|---|
1 2 3 4 | |
这会导致 SQL 错误,因为 WHERE 子句是在聚合之前执行的,COUNT(*) 尚未计算出来。
正确示例(使用 HAVING 过滤聚合结果):
| SQL | |
|---|---|
1 2 3 4 | |
执行过程:
- 分组: SQL 先根据
grade对数据进行分组。 - 聚合: 然后计算每个
grade组的学生数量 (COUNT(*))。 - 过滤: 最后,
HAVING子句对聚合结果进行过滤,只保留学生数量大于 2 的组。
Warning
- WHERE:在数据分组和聚合之前过滤表中的行
- HAVING:在数据分组和聚合之后过滤表中的行
DISTINCT¶
去重
DISTINCT 的作用就是去除重复值,只保留唯一的值在查询结果中。如果你只关心有哪些不同的 grade 出现在表中,那么 DISTINCT 就非常有用。
| SQL | |
|---|---|
1 | |
假设你有一个 Association 表,其中包含学生的成绩(grade)信息:
| student_id | course_id | grade |
|---|---|---|
| 1 | 101 | A |
| 2 | 102 | B |
| 3 | 103 | A |
| 4 | 104 | C |
| 5 | 105 | B |
| 6 | 106 | A |
现在,你想查询这个表中所有不同的 grade(等级)种类。你可以使用以下查询:
| SQL | |
|---|---|
1 | |
这个查询会返回表中所有不同的 grade,并且每种 grade 只会出现一次:
| grade |
|---|
| A |
| B |
| C |
在这个示例中,尽管 grade 列中有多行记录是相同的(如 A 出现了三次,B 出现了两次),但 DISTINCT 关键字确保每种 grade 只在结果中出现一次。
使用场景
DISTINCT 通常用于你只想知道某个字段中存在哪些不同的值时。例如:
- 查询不同的课程类型:
SELECT DISTINCT course_type FROM Courses; - 查询不同的部门名称:
SELECT DISTINCT department FROM Employees;
如果你不使用 DISTINCT,例如:
| SQL | |
|---|---|
1 | |
那么你会得到如下结果,包含重复的 grade 值:
| grade |
|---|
| A |
| B |
| A |
| C |
| B |
| A |
这个查询会返回 grade 列中每一行的值,包括所有重复的 grade。
ORDER BY¶
排序
当我们希望对查询结果排序时,可以使用 ORDER BY,例如对课程的学分进行排序:
| SQL | |
|---|---|
1 | |
默认结果为升序。
我们也可以 人为规定降序(DESC关键字):
| SQL | |
|---|---|
1 | |
使用 ORDER BY 进行升序排序
假设你有一个 Courses 表,其中包含课程的标题和学分信息:
| course_id | title | credit |
|---|---|---|
| 1 | Mathematics | 3 |
| 2 | Computer Science | 4 |
| 3 | History | 2 |
| 4 | Physics | 5 |
| 5 | Chemistry | 3 |
使用下面的查询(ORDER BY 默认升序):
| SQL | |
|---|---|
1 2 3 | |
这个查询会返回按 credit 升序排列的课程列表:
| title | credit |
|---|---|
| History | 2 |
| Mathematics | 3 |
| Chemistry | 3 |
| Computer Science | 4 |
| Physics | 5 |
使用 ORDER BY 进行降序排序
如果你想按课程的学分从大到小排序,可以使用 DESC 关键字:
| SQL | |
|---|---|
1 2 3 | |
这个查询会返回按 credit 降序排列的课程列表:
| title | credit |
|---|---|
| Physics | 5 |
| Computer Science | 4 |
| Mathematics | 3 |
| Chemistry | 3 |
| History | 2 |
我们也可以 对多个字段排序,如优先按照学分降序,再按照课程名升序:
| SQL | |
|---|---|
1 | |
也可以对 GROUP BY 的结果排序,例如按照获得某等级的学生个数排升序:
| SQL | |
|---|---|
1 | |
Note
ORDER BY关键字默认升序排列,如果要降序排列,需要使用DESC关键字。
LIMIT¶
指定偏移与范围
| SQL | |
|---|---|
1 2 3 | |
例子:具体索引
LIMIT 可以对结果指定偏移与范围,比如我们查找学号最小的 10 位学生:
| SQL | |
|---|---|
1 | |
原理:升序排列,自上而下越来越大,用LIMIT取前 10 个。
例子:长页字段
假如这样的查询是要分给一个后端,每页10个,我们想要去查第 4 页的结果:
| SQL | |
|---|---|
1 | |
表示从第 30 位学生开始,查询 10 条记录。
SUB QUERY¶
tmp复用,嵌套查询
对于每次查询得到的结果集合,我们可以将其视为一个临时的数据表,可以(必须)对他起一个临时名称(别名)后继续进行 SELECT 等操作。而这样嵌套在查询中的查询称为 Sub Query,具有很强的表达能力,而且十分符合人类的思维直觉。
例如,我们希望查询 选课超过了平均值的学生的选课记录,可以将查询分为 3 步:
- 查询学生选课的数量:
SQL 1SELECT COUNT(*) cnt FROM Association GROUP BY student_id; - 对这些数量求平均:
其中 q1 是对第一个子查询的别名
SQL 1 2 3
SELECT AVG(q1.cnt) FROM ( SELECT COUNT(*) cnt FROM Association GROUP BY student_id ) q1; - 从 Association 表中筛选出相应的记录:
SQL 1 2 3 4 5
SELECT student_id, COUNT(*) cnt FROM Association GROUP BY student_id HAVING cnt > ( SELECT AVG(q1.cnt) FROM ( SELECT COUNT(*) cnt FROM Association GROUP BY student_id ) q1 );
子查询也可以用在 WHERE 语句中,例如希望查询选课数量排前 10 名学生的全部信息,可以将查询分为 3 步:
- 从
Association表中查出选课数量排前 10 名的学生的id:SQL 1 2 3 4 5
SELECT student_id, COUNT(*) cnt FROM Association GROUP BY student_id ORDER BY cnt DESC LIMIT 10; -- DESC + LIMIT(10) - 从中选出
id:其中SQL 1 2 3 4 5 6 7
SELECT student_id FROM ( SELECT student_id, COUNT(*) cnt FROM Association GROUP BY student_id ORDER BY cnt DESC LIMIT 10 ) top_ten;top_ten是对第一个子查询的别名 - 从
Students表中查出这些同学的信息:SQL 1 2 3 4 5 6 7 8 9
SELECT * FROM Students WHERE id in ( SELECT top_s.student_id FROM ( SELECT student_id, COUNT(*) cnt FROM Association GROUP BY student_id ORDER BY cnt DESC LIMIT 10 ) top_ten );
JOIN¶
Example¶
JOIN 是我们可以对多个表的结果按照某些列的约束关系进行拼接,常见的模式由:

例如,我们希望查看在选课记录中查看成绩的同时显示学生的名字:
| SQL | |
|---|---|
1 2 3 4 | |
其中 INNER JOIN 表示内连接,即只保留两个表中都存在的记录,ON 后面的条件表示两个表中的记录如何对应。
Tips
INNER JOIN 给出了两张表在指定字段交集上的笛卡尔积,用某些关键字将表衔接起来,你可以认为 INNER JOIN 等价于:
| Python | |
|---|---|
1 2 3 4 5 6 | |
也就是选出两张表笛卡尔积中符合谓词 P 的记录集合,数据库在执行时并不是这样实现的。
对于 INNER JOIN,我们可以认为其与 WHERE = 获得的结果一样,但实现原理却并不相同,JOIN 时使用 hashtable 进行比较,而 WHERE = 则是取笛卡尔积再过滤(即上述 Python 表示的代码)。因此前者效率为 O(\(logN\)),后者的效率为 O(\(N^2\))。
除了 INNER JOIN 外还有其他的 JOIN 方式,例如我们想看看每位同学都选了几门课,于是写了这样的查询:
| SQL | |
|---|---|
1 2 3 4 5 | |
结果发现第 50 名同学选了 0 门课并没有返回。这是因为 INNER JOIN 只返回至少存在一个匹配的记录。此时我们可以使用 LEFT JOIN,即无论是否存在匹配,都显示左侧表中的全部记录。对于不存在对应右表记录的左表记录,填充 NULL:
| SQL | |
|---|---|
1 2 3 4 5 | |
这样就会显示全部学生。
JOIN details¶
在 SQL 中,JOIN 操作用于将两个或多个表中的数据结合在一起。不同的 JOIN 类型有不同的行为,决定了如何结合这些数据。这里是一些常用的 JOIN 类型:
- INNER JOIN: 只返回两个表中匹配的记录。即,只有在两个表中都存在的记录才会出现在结果中。
- LEFT JOIN (或 LEFT OUTER JOIN): 返回左表中的所有记录,以及右表中匹配的记录。如果右表中没有匹配的记录,则结果中的右表列会填充
NULL。 - RIGHT JOIN (或 RIGHT OUTER JOIN): 返回右表中的所有记录,以及左表中匹配的记录。如果左表中没有匹配的记录,则结果中的左表列会填充
NULL。 - FULL JOIN (或 FULL OUTER JOIN): 返回两个表中的所有记录,对于没有匹配的记录,结果中相应的表列会填充
NULL。
假设你有两个表:
Students表记录了所有学生的信息:
| id | sid |
|---|---|
| 1 | 50 |
| 2 | 51 |
| 3 | 52 |
Association表记录了学生和课程的关联:
| student_id | course_id |
|---|---|
| 1 | 101 |
| 1 | 102 |
| 2 | 103 |
INNER JOIN 示例
| SQL | |
|---|---|
1 2 3 4 5 | |
结果:
| sid | course_count |
|---|---|
| 50 | 2 |
| 51 | 1 |
这个查询只返回那些在 Association 表中有记录的学生。因为 INNER JOIN 只包含两个表中都有的记录,所以 sid 为 52 的学生被排除了(因为他没有选课记录)。
LEFT JOIN 示例
| SQL | |
|---|---|
1 2 3 4 5 | |
结果:
| sid | course_count |
|---|---|
| 50 | 2 |
| 51 | 1 |
| 52 | 0 |
这个查询返回了所有学生,不管他们是否有选课记录。LEFT JOIN 确保了左表(Students)中的所有记录都会出现在结果中。如果右表(Association)中没有匹配记录,则相应的计数为 0。
Warning
注意区分谁是“左表”!!!
INNER JOIN: 只包括在两个表中都存在的记录。如果某个学生没有在Association表中有对应的记录(比如学生 ID 为 52 的学生),那么他不会出现在结果中。LEFT JOIN: 包括左表中的所有记录。即使右表中没有匹配的记录,也会显示左表中的记录,并用NULL或默认值填充右表的列。在这里,COUNT(Association.student_id)对于没有匹配的记录会是 0。
Functions¶
作为一门(编程)语言,MySQL 提供了 极为丰富的函数库,下面举几个字符串函数的例子。
Tips
SUBSTRING: 用于从字符串中提取特定位置的子字符串。CHAR_LENGTH: 用于计算字符串的长度。REPLACE: 用于在字符串中替换某些字符或子字符串。
SUBSTRING
查看姓名从第 1 个字符开始的 3 个字符:
| SQL | |
|---|---|
1 | |
CHAR_LENGTH
看看谁的名字比较长:
| SQL | |
|---|---|
1 2 3 4 | |
REPLACE
对字符串进行替换,比如我认为 Logic 是敏感词,在查询时将其替换为 **:
| SQL | |
|---|---|
1 | |
举个例子🌰
例子,假设 Students 表中包含以下数据:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
| 4 | David |
| 5 | Eve |
你想查看每个学生名字从第 1 个字符开始的 3 个字符,可以使用以下查询:
| SQL | |
|---|---|
1 2 | |
结果:
| id | name | short_name |
|---|---|---|
| 1 | Alice | Ali |
| 2 | Bob | Bob |
| 3 | Charlie | Cha |
| 4 | David | Dav |
| 5 | Eve | Eve |
假设你想找出名字最长的学生,并按照名字长度排序。可以使用以下查询:
| SQL | |
|---|---|
1 2 3 4 | |
结果:
| id | name | name_len |
|---|---|---|
| 3 | Charlie | 7 |
| 4 | David | 5 |
| 1 | Alice | 5 |
| 2 | Bob | 3 |
| 5 | Eve | 3 |
假设 Courses 表中包含以下数据:
| cid | title |
|---|---|
| 101 | Introduction to Logic |
| 102 | Advanced Logic |
| 103 | Discrete Mathematics |
| 104 | Computational Theory |
| 105 | Logic and Algorithms |
你想将课程标题中的敏感词 "Logic" 替换为 "**",可以使用以下查询:
| SQL | |
|---|---|
1 2 | |
结果:
| cid | sanitized_title |
|---|---|
| 101 | Introduction to ** |
| 102 | Advanced ** |
| 103 | Discrete Mathematics |
| 104 | Computational Theory |
| 105 | ** and Algorithms |
UPDATE¶
| SQL | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
例如我要更新 History of Musical Theater 课程的学分:
| SQL | |
|---|---|
1 | |
UPDATE 过程中可以直接使用表中的值:
| SQL | |
|---|---|
1 | |
DELETE¶
| SQL | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
删除 History of Musical Theater 课程的选课记录:
| SQL | |
|---|---|
1 2 3 | |
在多表删除中,只有 DELETE 和 FROM 之间列举的表中的记录会被删除 (在这里就是Association),FROM 后出现但是不在 DELETE 和 FROM 之间的表仅用作查询的参考。
值得注意的是,DELETE并不会立即释放磁盘空间:
| SQL | |
|---|---|
1 | |
其中:
Data_length为数据文件的大小(以字节为单位)Index_length为索引文件的大小(以字节为单位)Data_length + Index_length表示表的总大小
我们删除一些数据:
| SQL | |
|---|---|
1 | |
在此查询,发现即使 Association 空了,但是表的大小没有发生变化,为了释放空间,我们可以使用:
| SQL | |
|---|---|
1 2 3 | |
APPENDIX¶
EXPLAIN ANALYZE¶
你可能关心查询是如何被数据库执行的,这时就可以用 EXPLAIN ANALYZE,让 MySQL 打印执行计划,例如:
| SQL | |
|---|---|
1 2 3 4 5 6 | |
SQL Constraints¶
我们在前面看到的 NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY 等均为约束,用于对字段指定数据规则。
UNIQUE不只可以约束一列,也可以约束多列(联合唯一);DEFAULT约束可以用于指定默认值,用于插入时没有指定值得情况;
让我们创建一个示例表,展示常见的 SQL 约束,如 NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, 和 DEFAULT。这个表将包含学生信息和他们选修的课程,展示如何在表定义中使用这些约束。
1) 创建 Students 表
这个表包含学生的基本信息,我们将使用 PRIMARY KEY、UNIQUE 和 NOT NULL 约束。
| SQL | |
|---|---|
1 2 3 4 5 6 7 | |
2) 创建 Courses 表
这个表包含课程信息,展示了 PRIMARY KEY 和 UNIQUE 约束的用法。
| SQL | |
|---|---|
1 2 3 4 5 | |
3) 创建 Association 表
这个表用来表示学生与课程的关联,展示了 FOREIGN KEY 和联合 UNIQUE 约束的用法。
| SQL | |
|---|---|
1 2 3 4 5 6 7 8 | |
解释:
-
PRIMARY KEY:Students表中的id列和Courses表中的cid列使用了PRIMARY KEY约束,确保每行都有一个唯一标识符。- 在
Association表中,我们使用student_id和course_id的组合作为联合主键,确保一个学生在Association表中不能重复选同一门课程。
-
UNIQUE:- 在
Students表中,sid列和email列使用了UNIQUE约束,确保每个学生的学号和邮箱都是唯一的。 Courses表中的title列也有UNIQUE约束,确保每门课程的名称是唯一的。
- 在
-
NOT NULL:Students表中的sid和name列,Courses表中的title列,以及Association表中的student_id和course_id列都使用了NOT NULL约束,确保这些列不能存储空值。
-
DEFAULT:- 在
Students表中的registration_date列和Courses表中的credit列使用了DEFAULT约束。如果插入时没有指定值,系统会自动使用默认值。
- 在
-
FOREIGN KEY:- 在
Association表中,student_id和course_id列分别作为外键,引用Students表中的id列和Courses表中的cid列。这确保了Association表中的学生和课程必须存在于各自的表中。
- 在