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 1
SELECT 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
表中的学生和课程必须存在于各自的表中。
- 在