# SQLite综合能力考核：大学数据库管理实战

## 介绍：大学数据库场景

为了全面考察您对SQLite的掌握程度，我们将围绕一个清晰、易于理解的场景来设计所有练习。这种叙事性的方法不仅能提升学习的趣味性，还能帮助您理解SQL命令在实际工作中的应用。

**场景描述：** 您将为一所小型大学设计并管理一个数据库。该数据库需要追踪讲师信息、他们教授的课程、注册课程的学生以及学生的选课和成绩详情。这是一个经典的关系模型，非常适合用来测试DDL（数据定义语言）、DML（数据操作语言）、DQL（数据查询语言）和TCL（事务控制语言）的各项概念。

**实体与关系：**

+   **讲师 (Instructors):** 每位讲师拥有姓名、邮箱和办公室编号。

+   **课程 (Courses):** 每门课程拥有课程名称、所属系别、学分，并由一位讲师授课。

+   **学生 (Students):** 每位学生拥有姓名、邮箱和专业。

+   **选课记录 (Enrollments):** 这是一个连接表，用于关联学生和课程，并记录学生在特定课程中获得的成绩。


这个场景经过精心选择，因为它天然地包含了**一对多关系**（一位讲师可以教授多门课程）和**多对多关系**（一名学生可以选修多门课程，一门课程也可以被多名学生选修），为测试连接查询和参照完整性提供了坚实的基础。

* * *

## 第一部分：DDL (数据定义语言) - 构建数据库基础

本部分旨在考察您将业务需求转化为逻辑数据库结构的能力。这不仅考验您的DDL命令语法，还涉及数据规范化和数据完整性等设计原则。我们将重点考察 `CREATE TABLE`、数据类型与类型亲和性、`PRIMARY KEY`、`FOREIGN KEY`、`NOT NULL`、`UNIQUE`、`DEFAULT` 以及 `ALTER TABLE` 等命令。

**题目列表：**

1.  在SQLite中，外键约束默认是关闭的，这可能导致数据完整性问题 。为了确保后续操作的参照完整性，请写出为当前数据库连接开启外键约束强制执行的命令。  
    
2.  创建一个名为 `Instructors` 的表，用于存储讲师信息。该表应包含以下列：

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

    +   `first_name`: 文本类型，不能为空。

    +   `last_name`: 文本类型，不能为空。

    +   `email`: 文本类型，不能为空，且必须保证其值唯一。

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

    +   `student_id`: 整数类型，作为主键。

    +   `first_name`: 文本类型，不能为空。

    +   `last_name`: 文本类型，不能为空。

    +   `email`: 文本类型，不能为空，且值唯一。

    +   `major`: 文本类型，如果未指定，则默认值为 'Undeclared'。

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

    +   `course_id`: 整数类型，作为主键。

    +   `title`: 文本类型，不能为空。

    +   `department`: 文本类型，不能为空（例如 'CSCI'）。

    +   `credits`: 整数类型。

    +   `instructor_id`: 整数类型，该列应作为外键，引用 `Instructors` 表的 `instructor_id` 列。

5.  修改 `Courses` 表中 `instructor_id` 的外键约束。要求当 `Instructors` 表中的某位讲师记录被删除时，`Courses` 表中由该讲师授课的课程的 `instructor_id` 字段应自动被设置为 `NULL` 。  
    
6.  创建一个名为 `Enrollments` 的连接表，用于记录学生选修课程的情况。该表应包含：

    +   `student_id`: 整数类型。

    +   `course_id`: 整数类型。

    +   `grade`: 文本类型，用于存储成绩（如 'A', 'B+', 'C' 等）。

    +   将 `student_id` 和 `course_id` 的组合设置为主键（复合主键）。  

假设学校规定，每个学生最多只能选修 5 门课程。仅在数据库层面（不依赖应用程序代码），我们如何实现这个约束？

（提示：查阅 SQLite 关于 CHECK 约束和子查询的文档，思考一个创造性的解决方案。）
        
7.  在 `Enrollments` 表上定义外键约束：

    +   `student_id` 列引用 `Students` 表的 `student_id` 列。

    +   `course_id` 列引用 `Courses` 表的 `course_id` 列。

8.  修改 `Enrollments` 表的外键约束。要求当 `Students` 表中的某个学生记录被删除时，该学生所有的选课记录也应自动从 `Enrollments` 表中被删除（级联删除）。  
    
9.  使用 `ALTER TABLE` 命令为 `Instructors` 表添加一个新列，名为 `office_number`，类型为 `TEXT`。

10.  使用 `ALTER TABLE` 命令将 `Enrollments` 表重命名为 `Student_Enrollments`。

11.  创建一个名为 `Temp_Table` 的临时表（列定义不限），然后写出删除该表的命令。


* * *

## 第二部分：DML (数据操作语言) - 填充与修改数据

本部分旨在考察您能否正确地插入、更新和删除数据，同时遵守在DDL阶段定义的各项约束。DML操作的成功与否，将直接反映您DDL设计的正确性。

**题目列表：**

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

    +   (1, 'Alan', 'Turing', 'alan.turing@bletchley.edu', 'B-101')

    +   (2, 'Grace', 'Hopper', 'grace.hopper@yale.edu', 'C-203')

    +   (3, 'John', 'von Neumann', 'john.vn@ias.edu', 'A-305')

2.  向 `Students` 表中插入至少五名学生的数据，确保他们的 `student_id` 从101开始，并拥有不同的专业。

    SQL

    ```
    INSERT INTO Students (student_id, first_name, last_name, email, major) VALUES
    (101, 'Alice', 'Smith', 'alice.smith@university.edu', 'Computer Science'),
    (102, 'Bob', 'Johnson', 'bob.johnson@university.edu', 'Physics'),
    (103, 'Charlie', 'Brown', 'charlie.brown@university.edu', 'History'),
    (104, 'Diana', 'Prince', 'diana.prince@university.edu', 'Computer Science'),
    (105, 'Eve', 'Adams', 'eve.adams@university.edu', 'Physics');
    ```

3.  根据第一步插入的讲师ID，向 `Courses` 表中插入以下课程数据：

    SQL

    ```
    INSERT INTO Courses (course_id, title, department, credits, instructor_id) VALUES
    (201, 'Introduction to CS', 'CSCI', 3, 1),
    (202, 'Advanced Compilers', 'CSCI', 4, 2),
    (203, 'Game Theory', 'MATH', 3, 3),
    (204, 'Databases', 'CSCI', 4, 1),
    (205, 'Quantum Mechanics', 'PHYS', 4, NULL);
    ```

4.  向 `Student_Enrollments` 表（注意，该表已在DDL部分被重命名）中插入以下选课记录：

    SQL

    ```
    INSERT INTO Student_Enrollments (student_id, course_id, grade) VALUES
    (101, 201, 'A'),
    (101, 204, 'B+'),
    (102, 205, 'A-'),
    (103, 203, 'B'),
    (104, 201, 'C'),
    (104, 202, 'A');
    ```

5.  **测试 `NOT NULL` 约束**：尝试向 `Instructors` 表中插入一条记录，但 `first_name` 字段留空。此操作预期会失败。请写出这条 `INSERT` 语句。

6.  **测试 `UNIQUE` 约束**：尝试向 `Students` 表中插入一名新学生，其 `email` 与 'alice.smith@university.edu' 相同。此操作预期会因违反唯一性约束而失败。请写出这条 `INSERT` 语句。

7.  **测试 `FOREIGN KEY` 约束**：尝试向 `Courses` 表中插入一门新课程，其 `instructor_id` 为999，而这个ID在 `Instructors` 表中并不存在。此操作预期会因违反外键约束而失败。请写出这条 `INSERT` 语句。

8.  Alan Turing博士更换了办公室。请使用 `UPDATE` 语句，将他的 `office_number` 更新为 'B-105'。

9.  学校决定将 'CSCI' 系的缩写统一更改为 'COMP'。请使用 `UPDATE` 语句更新 `Courses` 表中所有 'CSCI' 系的课程。

**测试 `连表查询`** 查询学生'Bob Johnson' (student_id=102) 选取的所有课程信息

10.  **测试 `ON DELETE CASCADE`**：学生 'Bob Johnson' (student\_id=102) 退学。请从 `Students` 表中删除他的记录。删除后，请查询 `Student_Enrollments` 表，验证他的选课记录是否也已被自动删除。

11.  **测试 `ON DELETE SET NULL`**：讲师 'Grace Hopper' (instructor\_id=2) 离职。请从 `Instructors` 表中删除她的记录。删除后，请查询 `Courses` 表，验证她所教授课程的 `instructor_id` 是否已变为 `NULL`。

12.  删除 `Student_Enrollments` 表中所有成绩为 'C' 的记录。


* * *

## 第三部分：DQL (数据查询语言) - 查询与分析数据

本部分将全面考察您从数据库中检索和分析数据的能力，从简单的单表查询逐步过渡到复杂的多表聚合查询。

**题目列表：**

#### 基础检索 (1-10)

1.  查询并显示 `Students` 表中的所有列及所有行。

2.  查询所有讲师的姓（`last_name`）和名（`first_name`）。

3.  查询所有属于 'COMP' 系（假设已在DML部分更新）的课程名称（`title`）。

4.  查询所有专业为 'Physics' 的学生信息，并按姓氏（`last_name`）升序排列。

5.  查询学分（`credits`）最高的3门课程的所有信息。

6.  查询 `Students` 表中所有不重复的专业（`major`）名称。

7.  查询课程名称（`title`）中包含 'Intro' 关键字的所有课程。

8.  查询专业为 'History' 或 'Physics' 的所有学生信息。

9.  查询所有办公室编号（`office_number`）未被指定的讲师。

10.  查询所有学分在3到4之间（包含3和4）的课程名称和学分。


#### 聚合与分组 (11-20)

11.  统计 `Students` 表中共有多少名学生。

12.  计算 `Courses` 表中所有课程的平均学分是多少。

13.  统计选修了课程ID为201（'Introduction to CS'）的学生总人数。

14.  按专业（`major`）分组，统计每个专业的学生人数。

15.  按系别（`department`）分组，统计每个系别开设的课程数量。

16.  查询学生人数超过1人的专业名称以及对应的人数。

17.  按课程（`course_id`）分组，计算每门课程的平均成绩（提示：这在当前数据结构下无法直接计算，但请思考如何统计选课人数）。请统计每门课程的选课人数。

18.  查询开设课程总数超过1门的讲师ID及其开设的课程数。

19.  找出 `Courses` 表中学分最高和最低的课程的学分值。

20.  统计所有 'COMP' 系课程的总学分。


#### 连接查询与复杂查询 (21-32)

21.  使用 `INNER JOIN` 查询所有选修了 'Introduction to CS' 这门课的学生的姓和名。

22.  使用 `INNER JOIN` 连接 `Student_Enrollments` 和 `Students` 表，列出每条选课记录对应的学生全名（姓和名拼接）和成绩。

23.  使用 `INNER JOIN` 连接 `Courses` 和 `Instructors` 表，列出每位讲师的全名以及他们所教授的课程名称。

24.  **`LEFT JOIN` 应用**：查询所有讲师的全名，以及他们教授的课程名称。即使某位讲师没有教授任何课程，也需要显示在结果中（其课程名称显示为 `NULL`）。

25.  **`LEFT JOIN` 应用**：查询所有没有教授任何课程的讲师的全名。

26.  **`LEFT JOIN` 应用**：查询所有学生的姓名，以及他们选修的课程名称。即使某个学生没有选修任何课程，也需要显示在结果中。

27.  **`LEFT JOIN` 应用**：查询所有没有选修任何课程的学生的姓名。

28.  使用子查询，查询由 'Alan Turing' 教授的所有课程的名称。

29.  使用子查询，查询选修了至少一门 'COMP' 系课程的所有学生的姓名。

30.  查询选课数量最多的学生的姓名。

31.  使用 `CASE` 语句，查询所有学生的姓名和专业，并添加一列名为 `remark` 的备注。如果专业是 'Computer Science'，备注为 'Tech Major'；如果专业是 'Physics'，备注为 'Science Major'；否则备注为 'Arts & Humanities'。

32.  **逻辑执行顺序理解**：尝试按专业统计学生人数，并将统计结果列命名为 `student_count`。然后，使用 `WHERE student_count > 1` 来筛选结果。这个查询会失败。请解释为什么会失败，并写出使用 `HAVING` 子句的正确查询 。  
    

* * *

## 第四部分：TCL (事务控制语言) - 保证数据一致性

本部分旨在考察您对事务的理解，以及如何使用事务来确保在执行多步操作时数据库的原子性和一致性。ACID原则是数据库管理的核心概念之一 。  

**题目列表：**

1.  **基本事务**：开启一个事务，向 `Students` 表中插入一名新学生 (student\_id: 106, name: 'Frank', 'Castle', email: 'frank.castle@university.edu', major: 'Law')，然后提交事务，使更改永久生效。

2.  **`ROLLBACK` 场景**：开启一个事务，删除 `Students` 表中所有专业为 'Undeclared' 的学生，然后执行 `ROLLBACK` 命令。最后，查询 `Students` 表，验证被删除的学生记录是否已恢复。

3.  **原子操作场景**：学生 'Alice Smith' (student\_id: 101) 决定从 'Databases' (course\_id: 204) 这门课退选，并改选 'Game Theory' (course\_id: 203)。这个操作需要两步：从 `Student_Enrollments` 中删除一条记录，并插入一条新记录。请将这两个操作放在一个事务中，确保它们要么都成功，要么都失败。

4.  **约束冲突与自动回滚**：开启一个事务。首先，将学生 'Charlie Brown' (student\_id: 103) 的专业更新为 'Philosophy'。然后，尝试插入一名新学生，但其 `email` 与已存在的学生 'Diana Prince' 冲突。由于第二步违反了 `UNIQUE` 约束会失败，整个事务会自动回滚 。请问，'Charlie Brown' 的专业最终是什么？请通过查询验证。  
    
5.  **`SAVEPOINT` 应用**：在一个事务中，按顺序执行以下操作： a. 插入两名新的 'Art' 专业学生。 b. 创建一个名为 `after_art_students` 的保存点。 c. 插入两名新的 'Music' 专业学生。 d. 回滚到 `after_art_students` 这个保存点。 e. 提交事务。 请问，最终哪些专业的学生被永久保存到了数据库中？

6.  **复杂原子操作**：为一名学生办理毕业手续。这需要：a) 从 `Student_Enrollments` 表中删除该学生的所有选课记录；b) 从 `Students` 表中删除该学生的记录。请为学生 'Diana Prince' (student\_id: 104) 编写一个事务来完成此操作，确保数据的一致性。

7.  根据上一题的毕业操作，解释ACID属性中的 'A' (Atomicity - 原子性) 是如何体现的。

8.  以 `Student_Enrollments` 表的外键约束为例，解释ACID属性中的 'C' (Consistency - 一致性) 是如何保证的。

9.  解释在何种情况下，您可能会选择使用 `BEGIN IMMEDIATE TRANSACTION` 而不是默认的延迟事务（`BEGIN DEFERRED TRANSACTION`）。  
    
10.  一个脚本需要更新100门不同课程的学分。相比于让SQLite为每条 `UPDATE` 语句自动提交事务，为什么将这100条语句包裹在一个单独的事务中会更高效？


* * *

## 答案

### 第一部分：DDL 答案

1.  SQL

    ```
    PRAGMA foreign_keys = ON;
    ```

2.  SQL

    ```
    CREATE TABLE Instructors (
        instructor_id INTEGER PRIMARY KEY AUTOINCREMENT,
        first_name TEXT NOT NULL,
        last_name TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE
    );
    ```

3.  SQL

    ```
    CREATE TABLE Students (
        student_id INTEGER PRIMARY KEY,
        first_name TEXT NOT NULL,
        last_name TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE,
        major TEXT DEFAULT 'Undeclared'
    );
    ```

4.  SQL

    ```
    CREATE TABLE Courses (
        course_id INTEGER PRIMARY KEY,
        title TEXT NOT NULL,
        department TEXT NOT NULL,
        credits INTEGER,
        instructor_id INTEGER,
        FOREIGN KEY (instructor_id) REFERENCES Instructors(instructor_id)
    );
    ```

5.  \-- 需要先删除旧表，再用新的约束创建。在实际操作中，这意味着需要备份数据。

    SQL

    ```
    -- 假设表已存在，需要先删除再重建（或使用ALTER TABLE在新版SQLite中）
    -- 为简化题目，这里提供带ON DELETE SET NULL的完整创建语句
    DROP TABLE Courses;
    CREATE TABLE Courses (
        course_id INTEGER PRIMARY KEY,
        title TEXT NOT NULL,
        department TEXT NOT NULL,
        credits INTEGER,
        instructor_id INTEGER,
        FOREIGN KEY (instructor_id) REFERENCES Instructors(instructor_id) ON DELETE SET NULL
    );
    ```

6.  SQL

    ```
    CREATE TABLE Enrollments (
        student_id INTEGER,
        course_id INTEGER,
        grade TEXT,
        PRIMARY KEY (student_id, course_id)
    );
    ```

7.  \-- 同样，这里提供带完整外键定义的创建语句

    SQL

    ```
    DROP TABLE Enrollments;
    CREATE TABLE Enrollments (
        student_id INTEGER,
        course_id INTEGER,
        grade TEXT,
        PRIMARY KEY (student_id, course_id),
        FOREIGN KEY (student_id) REFERENCES Students(student_id),
        FOREIGN KEY (course_id) REFERENCES Courses(course_id)
    );
    ```

8.  \-- 提供带ON DELETE CASCADE的完整创建语句

    SQL

    ```
    DROP TABLE Enrollments;
    CREATE TABLE Enrollments (
        student_id INTEGER,
        course_id INTEGER,
        grade TEXT,
        PRIMARY KEY (student_id, course_id),
        FOREIGN KEY (student_id) REFERENCES Students(student_id) ON DELETE CASCADE,
        FOREIGN KEY (course_id) REFERENCES Courses(course_id)
    );
    ```

9.  SQL

    ```
    ALTER TABLE Instructors ADD COLUMN office_number TEXT;
    ```

10.  SQL

```
ALTER TABLE Enrollments RENAME TO Student_Enrollments;
```

11.  SQL

```
CREATE TABLE Temp_Table (id INTEGER);
DROP TABLE Temp_Table;
```


### 第二部分：DML 答案

1.  SQL

    ```
    -- 注意：在DDL第9题后，Instructors表结构已改变。
    -- 为确保数据插入成功，需要更新instructor_id=1的记录。
    INSERT INTO Instructors (instructor_id, first_name, last_name, email) VALUES
    (1, 'Alan', 'Turing', 'alan.turing@bletchley.edu'),
    (2, 'Grace', 'Hopper', 'grace.hopper@yale.edu'),
    (3, 'John', 'von Neumann', 'john.vn@ias.edu');
    UPDATE Instructors SET office_number = 'B-101' WHERE instructor_id = 1;
    UPDATE Instructors SET office_number = 'C-203' WHERE instructor_id = 2;
    UPDATE Instructors SET office_number = 'A-305' WHERE instructor_id = 3;
    ```

2.  SQL

    ```
    INSERT INTO Students (student_id, first_name, last_name, email, major) VALUES
    (101, 'Alice', 'Smith', 'alice.smith@university.edu', 'Computer Science'),
    (102, 'Bob', 'Johnson', 'bob.johnson@university.edu', 'Physics'),
    (103, 'Charlie', 'Brown', 'charlie.brown@university.edu', 'History'),
    (104, 'Diana', 'Prince', 'diana.prince@university.edu', 'Computer Science'),
    (105, 'Eve', 'Adams', 'eve.adams@university.edu', 'Physics');
    ```

3.  SQL

    ```
    INSERT INTO Courses (course_id, title, department, credits, instructor_id) VALUES
    (201, 'Introduction to CS', 'CSCI', 3, 1),
    (202, 'Advanced Compilers', 'CSCI', 4, 2),
    (203, 'Game Theory', 'MATH', 3, 3),
    (204, 'Databases', 'CSCI', 4, 1),
    (205, 'Quantum Mechanics', 'PHYS', 4, NULL);
    ```

4.  SQL

    ```
    INSERT INTO Student_Enrollments (student_id, course_id, grade) VALUES
    (101, 201, 'A'),
    (101, 204, 'B+'),
    (102, 205, 'A-'),
    (103, 203, 'B'),
    (104, 201, 'C'),
    (104, 202, 'A');
    ```

5.  SQL

    ```
    INSERT INTO Instructors (last_name, email) VALUES ('Curie', 'marie.curie@sorbonne.edu');
    -- 这将返回一个错误，因为 first_name 违反了 NOT NULL 约束。
    ```

6.  SQL

    ```
    INSERT INTO Students (student_id, first_name, last_name, email, major)
    VALUES (106, 'Alex', 'Ray', 'alice.smith@university.edu', 'Biology');
    -- 这将返回一个错误，因为 email 违反了 UNIQUE 约束。
    ```

7.  SQL

    ```
    INSERT INTO Courses (course_id, title, department, credits, instructor_id)
    VALUES (206, 'Artificial Intelligence', 'CSCI', 4, 999);
    -- 这将返回一个错误，因为 instructor_id 违反了 FOREIGN KEY 约束。
    ```

8.  SQL

    ```
    UPDATE Instructors SET office_number = 'B-105' WHERE instructor_id = 1;
    ```

9.  SQL

    ```
    UPDATE Courses SET department = 'COMP' WHERE department = 'CSCI';
    ```

10.  SQL

```
DELETE FROM Students WHERE student_id = 102;
-- 查询验证 (应返回0行)
SELECT * FROM Student_Enrollments WHERE student_id = 102;
```

11.  SQL

```
DELETE FROM Instructors WHERE instructor_id = 2;
-- 查询验证 (instructor_id应为NULL)
SELECT * FROM Courses WHERE course_id = 202;
```

12.  SQL

```
DELETE FROM Student_Enrollments WHERE grade = 'C';
```


### 第三部分：DQL 答案

1.  `SELECT * FROM Students;`

2.  `SELECT last_name, first_name FROM Instructors;`

3.  `SELECT title FROM Courses WHERE department = 'COMP';`

4.  `SELECT * FROM Students WHERE major = 'Physics' ORDER BY last_name ASC;`

5.  `SELECT * FROM Courses ORDER BY credits DESC LIMIT 3;`

6.  `SELECT DISTINCT major FROM Students;`

7.  `SELECT * FROM Courses WHERE title LIKE '%Intro%';`

8.  `SELECT * FROM Students WHERE major IN ('History', 'Physics');`

9.  `SELECT * FROM Instructors WHERE office_number IS NULL;`

10.  `SELECT title, credits FROM Courses WHERE credits BETWEEN 3 AND 4;`

11.  `SELECT COUNT(*) FROM Students;`

12.  `SELECT AVG(credits) FROM Courses;`

13.  `SELECT COUNT(*) FROM Student_Enrollments WHERE course_id = 201;`

14.  `SELECT major, COUNT(*) FROM Students GROUP BY major;`

15.  `SELECT department, COUNT(*) FROM Courses GROUP BY department;`

16.  `SELECT major, COUNT(*) FROM Students GROUP BY major HAVING COUNT(*) > 1;`

17.  `SELECT course_id, COUNT(student_id) AS num_students FROM Student_Enrollments GROUP BY course_id;`

18.  `SELECT instructor_id, COUNT(*) FROM Courses GROUP BY instructor_id HAVING COUNT(*) > 1;`

19.  `SELECT MAX(credits), MIN(credits) FROM Courses;`

20.  `SELECT SUM(credits) FROM Courses WHERE department = 'COMP';`

21.  `SELECT s.first_name, s.last_name FROM Students s INNER JOIN Student_Enrollments se ON s.student_id = se.student_id INNER JOIN Courses c ON se.course_id = c.course_id WHERE c.title = 'Introduction to CS';`

22.  `SELECT s.first_name || ' ' | | s.last_name AS full_name, se.grade FROM Students s INNER JOIN Student_Enrollments se ON s.student_id = se.student_id;`

23. `SELECT i.first_name || ' ' | | i.last_name AS instructor_name, c.title FROM Instructors i INNER JOIN Courses c ON i.instructor_id = c.instructor_id;`

24. `SELECT i.first_name, i.last_name, c.title FROM Instructors i LEFT JOIN Courses c ON i.instructor_id = c.instructor_id;`

25. `SELECT i.first_name, i.last_name FROM Instructors i LEFT JOIN Courses c ON i.instructor_id = c.instructor_id WHERE c.course_id IS NULL;`

26. `SELECT s.first_name, s.last_name, c.title FROM Students s LEFT JOIN Student_Enrollments se ON s.student_id = se.student_id LEFT JOIN Courses c ON se.course_id = c.course_id;`

27. `SELECT s.first_name, s.last_name FROM Students s LEFT JOIN Student_Enrollments se ON s.student_id = se.student_id WHERE se.course_id IS NULL;`

28. `SELECT title FROM Courses WHERE instructor_id = (SELECT instructor_id FROM Instructors WHERE first_name = 'Alan' AND last_name = 'Turing');`

29. `SELECT first_name, last_name FROM Students WHERE student_id IN (SELECT student_id FROM Student_Enrollments WHERE course_id IN (SELECT course_id FROM Courses WHERE department = 'COMP'));`

30. `SELECT s.first_name, s.last_name FROM Students s JOIN Student_Enrollments se ON s.student_id = se.student_id GROUP BY s.student_id ORDER BY COUNT(se.course_id) DESC LIMIT 1;`

31. ```sql SELECT first_name, last_name, major, CASE major WHEN 'Computer Science' THEN 'Tech Major' WHEN 'Physics' THEN 'Science Major' ELSE 'Arts & Humanities' END AS remark FROM Students; ```

32. **解释**：失败是因为SQL的逻辑执行顺序是`FROM`->`WHERE`->`GROUP BY`->`HAVING`->`SELECT`->`ORDER BY`。`WHERE`子句在`SELECT`子句之前执行，因此在执行`WHERE`时，别名`student_count `尚未被创建。`HAVING`子句在`GROUP BY` 之后执行，用于过滤聚合后的结果，所以可以使用聚合函数。 **正确查询**： `sql SELECT major, COUNT(*) AS student_count FROM Students GROUP BY major HAVING COUNT(*) > 1;`

### 第四部分：TCL 答案

1.  SQL

    ```
    BEGIN TRANSACTION;
    INSERT INTO Students (student_id, first_name, last_name, email, major)
    VALUES (106, 'Frank', 'Castle', 'frank.castle@university.edu', 'Law');
    COMMIT;
    ```

2.  SQL

    ```
    BEGIN TRANSACTION;
    -- 假设有一个默认专业为'Undeclared'的学生
    INSERT INTO Students (student_id, first_name, last_name, email) VALUES (107, 'John', 'Doe', 'john.doe@university.edu');
    DELETE FROM Students WHERE major = 'Undeclared';
    ROLLBACK;
    -- 查询验证，John Doe 应该仍然存在
    SELECT * FROM Students WHERE student_id = 107;
    ```

3.  SQL

    ```
    BEGIN TRANSACTION;
    DELETE FROM Student_Enrollments WHERE student_id = 101 AND course_id = 204;
    INSERT INTO Student_Enrollments (student_id, course_id, grade) VALUES (101, 203, NULL);
    COMMIT;
    ```

4.  **最终专业**：'History'。因为事务中的第二步失败导致整个事务自动回滚，第一步的 `UPDATE` 操作也被撤销了。 **验证查询**：`SELECT major FROM Students WHERE student_id = 103;`

5.  **最终结果**：只有 'Art' 专业的学生被永久保存。因为事务回滚到了保存点 `after_art_students`，这撤销了插入 'Music' 专业学生的操作，然后提交了之前的操作。

6.  SQL

    ```
    BEGIN TRANSACTION;
    DELETE FROM Student_Enrollments WHERE student_id = 104;
    DELETE FROM Students WHERE student_id = 104;
    COMMIT;
    ```

7.  **原子性（Atomicity）** 体现在毕业操作的两个步骤（删除选课记录和删除学生记录）被视为一个不可分割的单元。如果其中任何一步失败（例如，由于权限问题），整个事务将回滚，数据库将恢复到操作开始前的状态，从而避免了“学生记录已删除但选课记录仍存在”这种不一致的状态。

8.  **一致性（Consistency）** 保证了事务将数据库从一个有效的状态转换到另一个有效的状态。`Student_Enrollments` 表的外键约束确保了任何插入的选课记录都必须关联一个真实存在的学生和一个真实存在的课程。任何试图破坏这种关系的DML操作（如为不存在的学生添加选课记录）都会失败，从而维护了数据库在事务前后的一致性。

9.  默认的延迟事务（`DEFERRED`）直到第一次实际读写数据库时才加锁。而 `IMMEDIATE` 事务在 `BEGIN` 命令执行时就立即尝试获取一个保留锁（写锁的前置锁）。当您预知即将进行写操作，并希望尽早确保事务不会因为其他写操作而失败（返回 `SQLITE_BUSY`）时，应使用 `IMMEDIATE`。这可以减少事务中途失败的可能性。

10.  将100条 `UPDATE` 语句包裹在单个事务中更高效，主要有两个原因：

+   **减少磁盘I/O**：SQLite的默认模式下，每次提交事务都需要将日志文件同步到磁盘，这是一个相对缓慢的操作。100次自动提交意味着100次磁盘同步。而单个事务只需要在最后 `COMMIT` 时进行一次磁盘同步，大大减少了I/O开销。

+   **原子性保证**：如果脚本在更新到第50条时失败，单个事务可以确保所有已做的更改都被回滚，数据库保持一致。而100个独立事务则会导致前49次更新已永久保存，数据库处于一个不完整的中间状态。