# MySQL综合能力考核：大学信息系统项目

## 导言：大学数据库项目 - 一次实践性考核

欢迎参加本次MySQL综合能力考核。本次考核旨在通过一个完整的、贴近实际的项目，全面评估您对MySQL数据库的掌握程度。您将扮演一名数据库工程师，负责为一个新成立的大学设计、构建并管理其核心的学生信息系统数据库。

这个项目不仅仅是一系列孤立的SQL命令测试，它模拟了数据库开发的整个生命周期：从根据业务需求定义数据结构（DDL），到填充和维护数据（DML），再到从中提取有价值的信息（DQL），最后确保复杂操作的数据一致性（TCL）。

### 场景概述与核心业务规则

大学信息系统的核心实体及其业务规则如下：

+   **讲师 (Instructors):** 每位讲师拥有唯一的ID、姓名、电子邮件和入职日期。电子邮件地址必须是唯一的。

+   **课程 (Courses):** 每门课程拥有唯一的ID、课程名称、学分，并且由一位讲师负责授课。

+   **学生 (Students):** 每位学生拥有唯一的ID、姓名、电子邮件、出生日期和专业。电子邮件地址必须是唯一的。

+   **选课记录 (Enrollments):** 这是连接学生和课程的关键。它记录了哪个学生选修了哪门课程，以及他们获得的分数。这体现了学生与课程之间的多对多关系。


### 实体关系描述 (ERD)

+   一名 **讲师 (Instructor)** 可以教授多门 **课程 (Courses)**。（一对多关系）

+   一名 **学生 (Student)** 可以选修多门 **课程 (Courses)**。

+   一门 **课程 (Course)** 可以被多名 **学生 (Student)** 选修。（学生与课程之间是多对多关系，因此需要`Enrollments`这个中间表来实现）


在开始编写任何SQL代码之前，深刻理解这些业务规则和实体间的关系至关重要。例如，“电子邮件必须唯一”这一业务规则将直接转化为数据库表中的`UNIQUE`约束；而“学生与课程的多对多关系”则决定了我们必须创建一个独立的`Enrollments`表，并通过外键将其与`Students`表和`Courses`表关联起来。这种从业务需求到数据库逻辑结构的转换，是数据库设计的核心思想，也是本次考核贯穿始终的考察重点。

现在，请根据以下题目，逐步完成这个大学数据库的构建与管理。

* * *

## 第一部分： foundational Architecture - 数据定义语言 (DDL)

本部分将考察您使用DDL创建和管理数据库基本结构的能力。一个设计良好、结构清晰的数据库是所有数据操作的基础。您将从零开始，构建起整个大学信息系统的骨架。

1.  创建一个名为 `university_db` 的新数据库，并设置其默认字符集为 `utf8mb4`。

2.  切换到 `university_db` 数据库。创建一个名为 `instructors` 的表，用于存储讲师信息。该表应包含以下列：

    +   `instructor_id`: 整数类型，作为主键，自动增长。

    +   `name`: 变长字符串类型，最大长度100，不允许为空。

    +   `email`: 变长字符串类型，最大长度100，必须唯一，不允许为空。

    +   `hire_date`: 日期类型，不允许为空。

3.  创建一个名为 `courses` 的表，用于存储课程信息。该表应包含以下列：

    +   `course_id`: 整数类型，作为主键，自动增长。

    +   `title`: 变长字符串类型，最大长度150，不允许为空。

    +   `credits`: 整数类型，不允许为空。

    +   `instructor_id`: 整数类型。此列将用于关联 `instructors` 表。

4.  创建一个名为 `students` 的表，用于存储学生信息。该表应包含以下列：

    +   `student_id`: 整数类型，作为主键，自动增长。

    +   `name`: 变长字符串类型，最大长度100，不允许为空。

    +   `email`: 变长字符串类型，最大长度100，必须唯一，不允许为空。

    +   `date_of_birth`: 日期类型。

    +   `major`: 变长字符串类型，最大长度50。

5.  创建最关键的连接表 `enrollments`，用于记录学生的选课情况。该表的设计需要体现多对多关系。它应包含以下列：

    +   `enrollment_id`: 整数类型，作为主键，自动增长。

    +   `student_id`: 整数类型，不允许为空。

    +   `course_id`: 整数类型，不允许为空。

    +   `grade`: 小数类型，总共3位，其中1位是小数（例如95.5）。

    +   **约束**:

        +   `student_id` 和 `course_id` 的组合必须是唯一的，以防止同一学生重复选修同一门课程。

        +   `student_id` 列应作为外键，引用 `students` 表的 `student_id` 列。

        +   `course_id` 列应作为外键，引用 `courses` 表的 `course_id` 列。

6.  在 `students` 表中添加一个新的列 `phone_number`，类型为变长字符串，最大长度为20。

7.  修改 `courses` 表中的 `credits` 列，为其添加一个 `CHECK` 约束，确保其值必须大于0。

8.  为 `students` 表的 `major` 列添加一个 `DEFAULT` 约束，如果插入新学生时未指定专业，则默认为 'Undeclared'。

9.  现在，为 `courses` 表中的 `instructor_id` 列添加一个外键约束，使其引用 `instructors` 表的 `instructor_id` 列。

10.  清空 `enrollments` 表中的所有数据，但保留表结构。请写出执行此操作的命令。

11.  完整地删除 `instructors` 表。

12.  解释 `DELETE FROM table_name;`、`TRUNCATE TABLE table_name;` 和 `DROP TABLE table_name;` 这三个命令在功能、性能和使用场景上的主要区别。


* * *

## 第二部分：Data Population and Management - 数据操作语言 (DML)

在数据库结构搭建完毕后，我们需要向其中填充数据，并根据业务变化进行维护。本部分将考察您使用DML语句进行数据插入、更新和删除的能力。您将亲身体验DDL中设置的约束是如何在DML操作中发挥作用的。

**准备工作：** 如果您在上一部分执行了第11题（删除了`instructors`表），请重新创建它，以确保后续操作可以正常进行。

13.  向 `instructors` 表中插入以下三位讲师的数据：

    +   姓名: '张磊', 邮箱: 'zhang.lei@university.edu', 入职日期: '2020-08-15'
        
    +   姓名: '李静', 邮箱: 'li.jing@university.edu', 入职日期: '2019-07-01'
        
    +   姓名: '王浩', 邮箱: 'wang.hao@university.edu', 入职日期: '2021-01-20'

14.  使用一条 `INSERT` 语句向 `students` 表中插入以下两位学生的数据：

    +   姓名: '赵辰', 邮箱: 'zhao.chen@student.edu', 生日: '2002-05-10', 专业: 'Computer Science'
        
    +   姓名: '孙悦', 邮箱: 'sun.yue@student.edu', 生日: '2003-09-22', 专业: 'Data Science'

15.  再向 `students` 表中插入一位学生，但不指定其专业，以测试默认值约束：

    +   姓名: '周毅', 邮箱: 'zhou.yi@student.edu', 生日: '2002-11-30'

16.  根据 `instructors` 表中的数据，向 `courses` 表中插入以下课程。请确保 `instructor_id` 与讲师姓名对应正确（假设张磊ID为1，李静ID为2，王浩ID为3）：

    +   课程名: 'Introduction to Programming', 学分: 4, 讲师ID: 1
        
    +   课程名: 'Advanced Databases', 学分: 4, 讲师ID: 2
        
    +   课程名: 'Web Development', 学分: 3, 讲师ID: 1
        
    +   课程名: 'Machine Learning', 学分: 3, 讲师ID: 3

17.  为学生选课。向 `enrollments` 表中插入以下记录（假设赵辰ID为1，孙悦ID为2，周毅ID为3；课程ID从1开始）：

    +   赵辰 选修 'Introduction to Programming'，成绩为 92.5
        
    +   赵辰 选修 'Advanced Databases'，成绩为 88.0
        
    +   孙悦 选修 'Introduction to Programming'，成绩为 95.0
        
    +   孙悦 选修 'Machine Learning'，成绩为 97.5

18.  **（挑战题）** 尝试向 `enrollments` 表中插入一条记录，其中 `student_id` 为 99（一个不存在的学生ID），`course_id` 为 1。执行该命令并解释为什么会失败。这个失败说明了什么？

19.  学生“赵辰”的邮箱地址输入有误，请将其邮箱更新为 'zhao.chen.new@student.edu'。

20.  由于教学计划调整，'Web Development' 这门课程的授课教师需要更换为“李静”（假设其ID为2）。请更新 `courses` 表。

21.  学校决定将所有 'Computer Science' 专业的学生的专业名称更新为 'Computer Science and Technology'。请执行此批量更新操作。

22.  学生“孙悦”决定退选 'Machine Learning' 这门课。请从 `enrollments` 表中删除对应的选课记录。

23.  **（挑战题）** 尝试从 `instructors` 表中删除讲师“张磊”（假设其ID为1）。执行该命令并解释为什么它很可能会失败。

24.  承接上一题，如果要成功删除讲师“张磊”，需要先执行哪些操作？请描述正确的操作步骤。


* * *

## 第三部分：Information Retrieval and Analysis - 数据查询语言 (DQL)

数据库的核心价值在于能够快速、准确地提取所需信息。本部分是考核的重点，将全面检验您使用DQL从简单查询到复杂分析的能力。所有查询都基于您在第二部分中插入的数据。

### A. 基础查询 (10道)

25.  查询 `students` 表中的所有学生信息。

26.  查询 `instructors` 表中所有讲师的姓名和邮箱地址。

27.  查询 `courses` 表中学分大于3的所有课程的完整信息。

28.  查询所有专业为 'Data Science' 的学生姓名和出生日期。

29.  查询课程名称中包含 'Introduction' 关键字的所有课程。

30.  查询所有姓“张”的讲师信息。

31.  查询在2020年1月1日之后入职的讲师姓名和入职日期。

32.  查询专业为 'Computer Science and Technology' 或 'Data Science' 的所有学生信息。

33.  查询 `enrollments` 表中所有成绩在85到95分之间（包含85和95）的选课记录。

34.  查询所有课程信息，并按学分从高到低排序。如果学分相同，则按课程名称的字母顺序排序。


### B. 聚合函数与分组查询 (10道)

35.  统计 `students` 表中共有多少名学生。

36.  计算 `courses` 表中所有课程的平均学分。

37.  找出 `enrollments` 表中记录的最高成绩。

38.  统计每个专业（major）分别有多少名学生。

39.  计算每门课程的平均成绩。查询结果应显示课程ID和对应的平均分。

40.  统计每位讲师（instructor）负责教授的课程数量。查询结果应显示讲师ID和课程数量。

41.  找出选修课程数量超过1门的学生的ID。

42.  查询每个专业的学生中，年龄最小的学生的出生日期。

43.  统计所有课程中，总共有多少名学生进行了选课（同一个学生选多门课算多次）。

44.  找出平均成绩最高的课程的ID。


### C. 连接查询 (10道)

45.  使用 `INNER JOIN` 查询所有学生的姓名以及他们选修的课程名称。

46.  查询所有选课记录，并同时显示学生的姓名、课程的名称以及该课程的分数。

47.  查询讲师“张磊”所教授的所有课程的名称和学分。

48.  列出所有学生及其选修课程的授课讲师的姓名。查询结果应包含三列：学生姓名, 课程名称, 讲师姓名。

49.  **（数据准备）** 为了测试 `LEFT JOIN`，请先插入一位没有选修任何课程的新学生：

    SQL
    
    ```
    INSERT INTO students (name, email, date_of_birth, major) VALUES ('钱坤', 'qian.kun@student.edu', '2004-01-15', 'Physics');
    ```
    
    现在，使用 `LEFT JOIN` 查询所有学生的姓名以及他们选修的课程名称。确保那位没有选课的学生（钱坤）也出现在结果中。

50.  **（数据准备）** 为了测试 `RIGHT JOIN`，请先插入一门没有任何学生选修的新课程：

    SQL
    
    ```
    INSERT INTO courses (title, credits, instructor_id) VALUES ('Quantum Physics', 4, 2);
    ```
    
    现在，查询所有课程的名称以及选修该课程的学生人数。确保那门没有学生选修的课程也出现在结果中，且学生人数为0。

51.  查询所有选修了学分等于4的课程的学生姓名，并去除重复的姓名。

52.  查询所有成绩高于90分的学生姓名和他们所选的课程名称。

53.  查询与“赵辰”在同一个专业的所有其他学生的姓名。

54.  列出所有讲师的姓名以及他们所教授课程的平均成绩。


### D. 高阶查询 (含`HAVING`子句与子查询) (6道)

55.  使用 `GROUP BY` 和 `HAVING` 子句，找出选修人数超过1人的所有课程的ID和选修人数。

56.  解释 `WHERE` 子句和 `HAVING` 子句在功能和使用时机上的核心区别。

57.  使用子查询，找出由讲师“李静”教授的所有课程的ID。然后基于这个ID列表，在 `enrollments` 表中找出所有选修了这些课程的学生的ID。

58.  使用子查询，查询选修了 'Advanced Databases' 这门课的所有学生的姓名。

59.  查询比所有 'Data Science' 专业学生的平均年龄都要大的学生姓名。（提示：需要计算 'Data Science' 学生的平均出生日期，然后进行比较）。

60.  查询每门课程中获得最高分的学生的姓名和该课程的名称。


* * *

## 第四部分：Ensuring Consistency - 事务控制语言 (TCL)

在真实的数据库应用中，一系列操作往往需要被视为一个不可分割的整体。例如，银行转账包含“A账户扣款”和“B账户收款”两个步骤，必须同时成功或同时失败。本部分将通过场景题，考察您使用TCL来保证复杂业务逻辑下数据一致性的能力。

61.  **场景一：成功注册** 学生“周毅”（ID为3）希望注册选修“Web Development”（ID为3）这门课程。这个注册过程包含两个步骤： a. 在 `enrollments` 表中插入一条新的选课记录（成绩暂时为NULL）。 b. 假设学校有一个 `student_accounts` 表（我们在此不创建，仅作为逻辑假设），需要更新该学生的账户余额，扣除相应学费。 请编写一个事务，将步骤a的操作包含在内。由于操作会成功，最终提交该事务。

62.  在上题的事务中，`COMMIT` 命令起到了什么作用？

63.  为什么将这两个步骤（或更多步骤）捆绑在一个事务中是至关重要的？

64.  启动一个事务的SQL命令是什么？

65.  **场景二：注册失败与回滚** 学生“钱坤”（ID为4）尝试注册“Machine Learning”（ID为4）课程，但系统检查发现他未满足前置课程要求，因此注册失败。整个操作流程如下： a. 开始一个事务。 b. 在 `enrollments` 表中插入一条选课记录。 c. 系统检查前置课程，发现不满足条件，因此决定中止操作。 请编写这个事务，并在操作中止时使用 `ROLLBACK` 命令，确保数据库状态恢复到事务开始之前。

66.  在上题的场景中，`ROLLBACK` 命令具体做了什么？

67.  如果在执行 `ROLLBACK` 之前，没有使用 `START TRANSACTION` 或等效命令开启事务，会发生什么？

68.  **场景三：带保存点的部分回滚** 一位管理员正在为学生“孙悦”（ID为2）批量注册三门课程。流程如下： a. 开始一个事务。 b. 成功注册第一门课 'Advanced Databases' (ID 2)。 c. 创建一个保存点，名为 `savepoint_1`。 d. 成功注册第二门课 'Web Development' (ID 3)。 e. 创建一个保存点，名为 `savepoint_2`。 f. 尝试注册第三门课 'Quantum Physics' (ID 5)，但发现该课程已满员，注册失败。 管理员决定保留前两次成功的注册，但撤销第三次失败的尝试。请编写SQL序列，使用 `SAVEPOINT` 和 `ROLLBACK TO` 来实现这个需求，并最终提交前两次成功的注册。

69.  在上题中，`ROLLBACK TO savepoint_2;` 命令执行后，数据库处于什么状态？

70.  为什么在这种多步操作中，使用 `SAVEPOINT` 比直接使用 `ROLLBACK` 更加灵活？

71.  `AUTOCOMMIT` 是MySQL中的一个重要概念。请解释它的含义，以及它与我们手动执行 `START TRANSACTION`、`COMMIT`、`ROLLBACK` 之间的关系。


* * *

## 附录：综合能力考核答案

### 第一部分：DDL答案

1.  SQL

    ```
    CREATE DATABASE university_db CHARACTER SET utf8mb4;
    ```

2.  SQL

    ```
    USE university_db;
    CREATE TABLE instructors (
        instructor_id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(100) NOT NULL,
        email VARCHAR(100) NOT NULL UNIQUE,
        hire_date DATE NOT NULL
    );
    ```

3.  SQL

    ```
    CREATE TABLE courses (
        course_id INT PRIMARY KEY AUTO_INCREMENT,
        title VARCHAR(150) NOT NULL,
        credits INT NOT NULL,
        instructor_id INT
    );
    ```

4.  SQL

    ```
    CREATE TABLE students (
        student_id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(100) NOT NULL,
        email VARCHAR(100) NOT NULL UNIQUE,
        date_of_birth DATE,
        major VARCHAR(50)
    );
    ```

5.  SQL

    ```
    CREATE TABLE enrollments (
        enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
        student_id INT NOT NULL,
        course_id INT NOT NULL,
        grade DECIMAL(3, 1),
        UNIQUE (student_id, course_id),
        FOREIGN KEY (student_id) REFERENCES students(student_id),
        FOREIGN KEY (course_id) REFERENCES courses(course_id)
    );
    ```

6.  SQL

    ```
    ALTER TABLE students ADD COLUMN phone_number VARCHAR(20);
    ```

7.  SQL

    ```
    ALTER TABLE courses ADD CONSTRAINT chk_credits CHECK (credits > 0);
    ```

8.  SQL

    ```
    ALTER TABLE students ALTER COLUMN major SET DEFAULT 'Undeclared';
    ```

9.  SQL

    ```
    ALTER TABLE courses ADD CONSTRAINT fk_instructor
    FOREIGN KEY (instructor_id) REFERENCES instructors(instructor_id);
    ```

10.  SQL

```
TRUNCATE TABLE enrollments;
```

11.  SQL

```
DROP TABLE instructors;
```

12.  **`DELETE FROM table_name;`**:

+   **功能**: 逐行删除表中的数据，可以与 `WHERE` 子句配合使用删除特定行。

+   **性能**: 相对较慢，因为它会为每一行记录删除日志。

+   **特点**: 触发`DELETE`触发器；不重置自增ID。 **`TRUNCATE TABLE table_name;`**:

+   **功能**: 快速删除表中的所有行。

+   **性能**: 非常快，因为它通过释放数据页来清空表，而不是逐行删除。

+   **特点**: 不触发`DELETE`触发器；通常会重置自增ID；不能与`WHERE`子句一起使用。 **`DROP TABLE table_name;`**:

+   **功能**: 完全删除整个表，包括表结构、数据、索引、约束和触发器。

+   **性能**: 非常快。

+   **特点**: 表将不复存在，需要`CREATE TABLE`才能重新使用。这是最具破坏性的操作。


### 第二部分：DML答案

13.  SQL

    ```
    INSERT INTO instructors (name, email, hire_date) VALUES
    ('张磊', 'zhang.lei@university.edu', '2020-08-15'),
    ('李静', 'li.jing@university.edu', '2019-07-01'),
    ('王浩', 'wang.hao@university.edu', '2021-01-20');
    ```

14.  SQL

    ```
    INSERT INTO students (name, email, date_of_birth, major) VALUES
    ('赵辰', 'zhao.chen@student.edu', '2002-05-10', 'Computer Science'),
    ('孙悦', 'sun.yue@student.edu', '2003-09-22', 'Data Science');
    ```

15.  SQL

    ```
    INSERT INTO students (name, email, date_of_birth) VALUES
    ('周毅', 'zhou.yi@student.edu', '2002-11-30');
    -- 查询后会发现他的major字段值为'Undeclared'
    ```

16.  SQL

    ```
    INSERT INTO courses (title, credits, instructor_id) VALUES
    ('Introduction to Programming', 4, 1),
    ('Advanced Databases', 4, 2),
    ('Web Development', 3, 1),
    ('Machine Learning', 3, 3);
    ```

17.  SQL

    ```
    INSERT INTO enrollments (student_id, course_id, grade) VALUES
    (1, 1, 92.5),
    (1, 2, 88.0),
    (2, 1, 95.0),
    (2, 4, 97.5);
    ```

18.  SQL

    ```
    INSERT INTO enrollments (student_id, course_id) VALUES (99, 1);
    ```
    
    **解释**: 该命令会失败，并报告一个外键约束错误 (`FOREIGN KEY constraint fails`)。 **原因**: 因为我们在 `enrollments` 表的 `student_id` 列上定义了一个外键，它引用了 `students` 表的 `student_id` 列。这意味着任何试图插入到 `enrollments` 表的 `student_id` 的值，都必须首先存在于 `students` 表中。由于 `students` 表中不存在ID为99的学生，数据库为了维护引用完整性，拒绝了这次插入操作。这完美地展示了DDL中定义的约束是如何保证数据一致性和有效性的。

19.  SQL

    ```
    UPDATE students SET email = 'zhao.chen.new@student.edu' WHERE name = '赵辰';
    ```

20.  SQL

    ```
    UPDATE courses SET instructor_id = 2 WHERE title = 'Web Development';
    ```

21.  SQL

    ```
    UPDATE students SET major = 'Computer Science and Technology' WHERE major = 'Computer Science';
    ```

22.  SQL

    ```
    DELETE FROM enrollments WHERE student_id = 2 AND course_id = 4;
    ```

23.  SQL

    ```
    DELETE FROM instructors WHERE instructor_id = 1;
    ```
    
    **解释**: 该命令很可能会失败，并报告一个外键约束错误。 **原因**: 因为讲师“张磊”（ID为1）在 `courses` 表中被引用了（'Introduction to Programming' 和 'Web Development' 这两门课的 `instructor_id` 都是1）。为了保护数据的引用完整性，数据库禁止删除一个正在被其他表引用的记录。

24.  **正确操作步骤**:

    1.  必须先处理掉 `courses` 表中对讲师“张磊”的引用。有两种方式：
        
        +   **方式一（重新分配课程）**: 将他教授的课程的 `instructor_id` 更新为另一位讲师的ID，或者设置为 `NULL`（如果该列允许为NULL）。
            
        +   **方式二（删除课程）**: 删除所有由他教授的课程（但这可能会引发连锁问题，因为课程可能已被学生选修）。
            
    2.  在解除了所有外键引用之后，才能安全地从 `instructors` 表中删除讲师“张磊”的记录。 例如，先执行更新：`UPDATE courses SET instructor_id = NULL WHERE instructor_id = 1;` 然后执行删除：`DELETE FROM instructors WHERE instructor_id = 1;`


### 第三部分：DQL答案

25.  `SELECT * FROM students;`

26.  `SELECT name, email FROM instructors;`

27.  `SELECT * FROM courses WHERE credits > 3;`

28.  `SELECT name, date_of_birth FROM students WHERE major = 'Data Science';`

29.  `SELECT * FROM courses WHERE title LIKE '%Introduction%';`

30.  `SELECT * FROM instructors WHERE name LIKE '张%';`

31.  `SELECT name, hire_date FROM instructors WHERE hire_date > '2020-01-01';`

32.  `SELECT * FROM students WHERE major IN ('Computer Science and Technology', 'Data Science');`

33.  `SELECT * FROM enrollments WHERE grade BETWEEN 85 AND 95;`

34.  `SELECT * FROM courses ORDER BY credits DESC, title ASC;`

35.  `SELECT COUNT(*) FROM students;`

36.  `SELECT AVG(credits) FROM courses;`

37.  `SELECT MAX(grade) FROM enrollments;`

38.  `SELECT major, COUNT(*) FROM students GROUP BY major;`

39.  `SELECT course_id, AVG(grade) FROM enrollments GROUP BY course_id;`

40.  `SELECT instructor_id, COUNT(*) FROM courses GROUP BY instructor_id;`

41.  `SELECT student_id FROM enrollments GROUP BY student_id HAVING COUNT(course_id) > 1;`

42.  `SELECT major, MAX(date_of_birth) FROM students GROUP BY major;` -- 年龄最小，出生日期最大

43.  `SELECT COUNT(*) FROM enrollments;`

44.  `SELECT course_id FROM enrollments GROUP BY course_id ORDER BY AVG(grade) DESC LIMIT 1;`

45.  `SELECT s.name, c.title FROM students s INNER JOIN enrollments e ON s.student_id = e.student_id INNER JOIN courses c ON e.course_id = c.course_id;`

46.  `SELECT s.name, c.title, e.grade FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id;`

47.  `SELECT c.title, c.credits FROM courses c JOIN instructors i ON c.instructor_id = i.instructor_id WHERE i.name = '张磊';`

48.  `SELECT s.name AS student_name, c.title AS course_title, i.name AS instructor_name FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id JOIN instructors i ON c.instructor_id = i.instructor_id;`

49.  `SELECT s.name, c.title FROM students s LEFT JOIN enrollments e ON s.student_id = e.student_id LEFT JOIN courses c ON e.course_id = c.course_id;`

50.  `SELECT c.title, COUNT(e.student_id) AS number_of_students FROM courses c LEFT JOIN enrollments e ON c.course_id = e.course_id GROUP BY c.course_id, c.title;`

51.  `SELECT DISTINCT s.name FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id WHERE c.credits = 4;`

52.  `SELECT s.name, c.title FROM students s JOIN enrollments e ON s.student_id = e.student_id JOIN courses c ON e.course_id = c.course_id WHERE e.grade > 90;`

53.  `SELECT s2.name FROM students s1 JOIN students s2 ON s1.major = s2.major WHERE s1.name = '赵辰' AND s2.name!= '赵辰';`

54.  `SELECT i.name, AVG(e.grade) AS average_grade FROM instructors i JOIN courses c ON i.instructor_id = c.instructor_id JOIN enrollments e ON c.course_id = e.course_id GROUP BY i.instructor_id, i.name;`

55.  `SELECT course_id, COUNT(student_id) AS num_students FROM enrollments GROUP BY course_id HAVING COUNT(student_id) > 1;`

56.  **`WHERE` vs `HAVING`**:

    +   **作用对象不同**: `WHERE` 子句作用于原始的表数据，在数据分组（`GROUP BY`）之前进行过滤。`HAVING` 子句作用于 `GROUP BY` 之后生成的结果集，对分组后的聚合结果进行过滤。
        
    +   **使用位置不同**: `WHERE` 必须在 `GROUP BY` 之前，`HAVING` 必须在 `GROUP BY` 之后。
        
    +   **可使用函数不同**: `WHERE` 子句中不能使用聚合函数（如 `COUNT()`, `AVG()`），而 `HAVING` 子句专门用于配合聚合函数进行条件判断。

57.  SQL

    ```
    SELECT student_id FROM enrollments
    WHERE course_id IN (
        SELECT course_id FROM courses
        WHERE instructor_id = (
            SELECT instructor_id FROM instructors WHERE name = '李静'
        )
    );
    ```

58.  SQL

    ```
    SELECT name FROM students
    WHERE student_id IN (
        SELECT student_id FROM enrollments
        WHERE course_id = (
            SELECT course_id FROM courses WHERE title = 'Advanced Databases'
        )
    );
    ```

59.  SQL

    ```
    SELECT name FROM students
    WHERE date_of_birth < (
        SELECT AVG(date_of_birth) FROM students WHERE major = 'Data Science'
    );
    ```

60.  SQL

    ```
    SELECT s.name, c.title
    FROM enrollments e
    JOIN students s ON e.student_id = s.student_id
    JOIN courses c ON e.course_id = c.course_id
    WHERE (e.course_id, e.grade) IN (
        SELECT course_id, MAX(grade)
        FROM enrollments
        GROUP BY course_id
    );
    ```


### 第四部分：TCL答案

61.  SQL

    ```
    START TRANSACTION;
    -- 步骤a: 插入选课记录
    INSERT INTO enrollments (student_id, course_id, grade) VALUES (3, 3, NULL);
    -- 步骤b: 假设的更新账户操作
    -- UPDATE student_accounts SET balance = balance - 100 WHERE student_id = 3;
    COMMIT;
    ```

62.  `COMMIT` 命令将事务中执行的所有更改（在此例中是 `INSERT` 语句）永久性地保存到数据库中。一旦提交，这些更改就成为数据库持久状态的一部分，并且对其他用户可见。

63.  事务的重要性在于保证**原子性（Atomicity）**。注册课程这个业务逻辑包含多个数据库操作，它们必须作为一个整体执行。如果只有插入选课记录成功，而扣费失败，那么数据就会处于不一致的状态。事务确保了这一系列操作要么全部成功（`COMMIT`），要么全部失败（`ROLLBACK`），从而维护了业务逻辑和数据的完整性。

64.  `START TRANSACTION;` (或者它的同义词 `BEGIN;`)

65.  SQL

    ```
    START TRANSACTION;
    -- 尝试插入选课记录
    INSERT INTO enrollments (student_id, course_id) VALUES (4, 4);
    -- 此时，应用逻辑发现前置课程不满足，决定中止
    ROLLBACK;
    ```

66.  `ROLLBACK` 命令会撤销当前事务中自 `START TRANSACTION` 以来所做的所有未提交的更改。在上题中，它会撤销那条 `INSERT` 语句，使得 `enrollments` 表恢复到事务开始前的状态，就好像那条 `INSERT` 语句从未执行过一样。

67.  如果MySQL的 `AUTOCOMMIT` 模式是开启的（默认情况），那么每一条SQL语句都会被视为一个独立的事务并被立即自动提交。在这种情况下，`ROLLBACK` 命令将不起任何作用，因为在它执行之前，前面的 `INSERT` 语句已经永久保存了。因此，要使用 `ROLLBACK`，必须先用 `START TRANSACTION` 显式地开启一个事务。

68.  SQL

    ```
    START TRANSACTION;
    -- 注册第一门课
    INSERT INTO enrollments (student_id, course_id) VALUES (2, 2);
    SAVEPOINT savepoint_1;
    -- 注册第二门课
    INSERT INTO enrollments (student_id, course_id) VALUES (2, 3);
    SAVEPOINT savepoint_2;
    -- 尝试注册第三门课 (此操作在真实场景中会失败，这里我们用回滚模拟)
    -- INSERT INTO enrollments (student_id, course_id) VALUES (2, 5); -- 假设失败
    -- 发现失败，回滚到第二个保存点
    ROLLBACK TO savepoint_2;
    -- 最终提交事务，保留前两次成功的注册
    COMMIT;
    ```

69.  执行 `ROLLBACK TO savepoint_2;` 之后，数据库的状态是：为孙悦注册第二门课的操作（`INSERT` course\_id 3）被撤销了，但注册第一门课的操作（`INSERT` course\_id 2）仍然保留在当前事务中，等待最终的 `COMMIT` 或 `ROLLBACK`。

70.  `SAVEPOINT` 提供了更细粒度的事务控制。在一个长事务中，如果某个中间步骤失败，我们不必撤销整个事务（`ROLLBACK`），而是可以选择性地回滚到某个已知的良好状态（`ROLLBACK TO SAVEPOINT`），然后继续执行其他操作或提交部分结果。这极大地增强了处理复杂业务逻辑时的灵活性。

71.  **`AUTOCOMMIT`** 是MySQL的一个系统变量，它控制着事务的提交方式。

    +   **当 `AUTOCOMMIT = 1` (默认值)**: 每一条SQL语句（如 `INSERT`, `UPDATE`, `DELETE`）在执行完毕后都会被立即自动提交，效果等同于在每条语句前后都加上了 `START TRANSACTION` 和 `COMMIT`。
        
    +   **当 `AUTOCOMMIT = 0`**: 自动提交被禁用。你需要手动执行 `START TRANSACTION` 来开启一个事务，并通过 `COMMIT` 或 `ROLLBACK` 来结束它。在手动结束之前，所有的更改都只在当前会话中可见，并且是未提交的状态。 **关系**: 当我们使用 `START TRANSACTION` 时，它会暂时覆盖当前的 `AUTOCOMMIT` 行为，创建一个手动控制的事务块。在这个块内，只有 `COMMIT` 或 `ROLLBACK` 才能结束事务，`AUTOCOMMIT` 的设置被忽略。这使得我们能够将多条语句捆绑在一起，实现TCL的核心功能。