数据库综合实验
1.需求分析
某大型图书馆需要开发一套数字化借阅管理系统,主要业务需求如下:
图书信息:记录图书的编号、书名、作者、ISBN、出版社、出版年份、页数、语言、库存数量、单价、分类。
读者信息:记录读者的证号、姓名、性别、身份证号、出生日期、所在单位/院系、联系电话、注册日期、读者类型(学生/教师/校外)、可借数量上限。
借阅信息:记录每次借阅的流水号、读者证号、图书编号、借书日期、应还日期、实际还书日期、续借次数、逾期罚款金额。
出版社信息:记录出版社编号、出版社名称、地址、联系电话、成立年份。
分类信息:记录分类编号、分类名称、上级分类(支持树形结构)。
业务规则:
一个读者可借多本书,一本书可被多个读者在不同时间借阅。
每本书只属于一个出版社(1:n),每本书属于一个分类(1:n)。
借书时库存>0,还书时自动增加库存,逾期计算罚款(触发器实现)。
2.逻辑结构设计
2.1 关系模式:
出版社(pid, pname, address, phone, founded_year) 主码:pid
分类(cid, cname, parentid) 主码:cid 外码:parentid → category.cid
图书(bno, bname, author, isbn, pid, cid, pubyear, stock, language, price) 主码:bno 外码:pid → publisher.pid 外码:cid → category.cid
读者(rno, rname, rsex, idcard, birthday, dept, phone, regdate, rtype, maxborrow) 主码:rno
借阅(borrowid, rno, bno, borrowdate, returndate, actualdate, renew_count, fine) 主码:borrowid 外码:rno → reader.rno 外码:bno → book.bno
2.3 第三范式
出版社:3NF 分类:3NF 图书:3NF 读者:3NF 借阅:3NF
理由: 所有非主属性均完全依赖主键; 不存在部分依赖; 不存在传递依赖; 因此均满足第三范式(3NF)。
2.4 m:n转换为关系模式
m:n联系转换方法:
若两个实体之间存在多对多联系, 则建立一个新的关系模式作为联系表, 并将两个实体的主码作为外码加入联系表。
例如:
学生(Student) 课程(Course)
存在选课关系
转换为:
SC( sno, cno, score )
其中: sno为Student外码 cno为Course外码
联合主键: (sno,cno)
3.创建数据库和表
3.1创建数据库
CREATE DATABASE library_db
CHARACTER SET utf8mb4;
USE library_db;
3.2创建数据库表
CREATE TABLE book(
bno CHAR(10) PRIMARY KEY,
bname VARCHAR(50) NOT NULL,
author VARCHAR(30),
press VARCHAR(30),
pubyear INT,
stock INT DEFAULT 1 CHECK(stock>=0)
);
CREATE TABLE reader(
rno CHAR(12) PRIMARY KEY,
rname VARCHAR(20) NOT NULL,
rsex CHAR(1)
CHECK(rsex IN('男','女')),
dept VARCHAR(40),
phone VARCHAR(20)
);
CREATE TABLE borrow(
borrowid INT AUTO_INCREMENT PRIMARY KEY,
rno CHAR(12) NOT NULL,
bno CHAR(10) NOT NULL,
borrowdate DATE NOT NULL,
returndate DATE NOT NULL,
actualdate DATE,
CONSTRAINT fk_reader
FOREIGN KEY(rno)
REFERENCES reader(rno)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT fk_book
FOREIGN KEY(bno)
REFERENCES book(bno)
ON DELETE RESTRICT
ON UPDATE CASCADE
);
desc book;
desc reader;
desc borrow;
3.3 添加修改删除字段
ALTER TABLE book
ADD pages INT;
ALTER TABLE reader
ADD email VARCHAR(50) UNIQUE;
验证:
desc book
desc reader
ALTER TABLE book
MODIFY author VARCHAR(50);
ALTER TABLE reader
MODIFY phone VARCHAR(20)
DEFAULT '无';
SHOW CREATE TABLE borrow;
ALTER TABLE book
DROP COLUMN pages;
ALTER TABLE borrow
DROP FOREIGN KEY fk_reader;
4.数据插入
INSERT INTO book VALUES
('B001','数据库系统概论','王珊',
'高等教育出版社',2018,5),
('B002','C程序设计语言','K&R',
'机械工业出版社',2015,3),
('B003','三体','刘慈欣',
'重庆出版社',2010,8),
('B004','计算机网络','谢希仁',
'电子工业出版社',2018,4);
INSERT INTO reader
VALUES
('R2024001','张明','男',
'计算机学院','13812345678',
'zhangming@qq.com'),
('R2024002','李芳','女',
'信息学院','13912345678',
'lifang@qq.com'),
('R2024003','王磊','男',
'文学院','13612345678',
'wanglei@qq.com');
INSERT INTO borrow VALUES
(1,'R2024001','B001',
'2025-03-01',
'2025-03-15',
'2025-03-14'),
(2,'R2024001','B003',
'2025-03-10',
'2025-03-24',
NULL),
(3,'R2024002','B002',
'2025-04-01',
'2025-04-15',
NULL),
(4,'R2024003','B004',
'2025-03-20',
'2025-04-03',
'2025-04-05');
4.1 设计违反外键约束和check约束并解释
INSERT INTO borrow(
rno,bno,
borrowdate,
returndate)
VALUES(
'R999999',
'B001',
'2025-05-01',
'2025-05-15');
错误:Cannot add or update a child row:
a foreign key constraint fails
原因:R999999在reader表中不存在,
违反参照完整性约束。
违反check
INSERT INTO reader
VALUES(
'R2024004',
'赵敏',
'未知',
'艺术学院',
'13712345678');
错误:Check constraint violated
原因:rsex字段只能取
男 或 女,
因此违反CHECK约束。
5.数据查询(DQL-基础查询)
1.select查询全部图书信息
select * from book;
2.查询库存大于3的图书
SELECT bno,bname,stock
FROM book
WHERE stock > 3;
3.查询2018年出版且库存不少于4本的图书
SELECT bno,bname,pubyear,stock
FROM book
WHERE pubyear = 2018 AND stock >= 4;
4.查询2015年至2018年出版的图书
SELECT bno,bname,pubyear
FROM book
WHERE pubyear BETWEEN 2015 AND 2018;
5.查询出版社为“重庆出版社”或“高等教育出版社”的图书
SELECT bno,bname,press
FROM book
WHERE press IN('重庆出版社','高等教育出版社');
6.查询书名中包含“计算机”的图书
SELECT bno,bname
FROM book
WHERE bname LIKE '%计算机%';
7.查询尚未归还的借阅记录
SELECT *
FROM borrow
WHERE actualdate IS NULL;
8.按库存数量降序查询图书
SELECT bno,bname,stock
FROM book
ORDER BY stock DESC;
9.查询库存最多的前2本图书
SELECT bno,bname,stock
FROM book
ORDER BY stock DESC
LIMIT 2;
10.查询所有不同的出版社
SELECT DISTINCT press
FROM book;
6.数据更新(update/delete)
1.将BOO1的库存修改为6
UPDATE book
SET stock = 6
WHERE bno = 'B001';
SELECT * FROM book WHERE bno='B001';
2.修改张明的院系和电话
UPDATE reader
SET dept = '人工智能学院',
phone = '18888888888'
WHERE rno = 'R2024001';
SELECT * FROM reader WHERE rno='R2024001';
3.将2018年出版图书库存统一增加1
UPDATE book
SET stock = stock + 1
WHERE pubyear = 2018;
SELECT * FROM book WHERE pubyear=2018;
4.删除一个测试读者
INSERT INTO reader
VALUES('R2024999','测试读者','男','测试学院','无');
DELETE FROM reader
WHERE rno = 'R2024999';
5.事务控制:更新后回滚
START TRANSACTION;
UPDATE book
SET stock = stock - 1
WHERE bno = 'B003';
SELECT * FROM book WHERE bno='B003';
ROLLBACK;
SELECT * FROM book WHERE bno='B003';
7.聚合查询与分组统计
1.统计图书总数
select count(*) as 图书总数 from book;
2.统计图书库存总量
select sum(stock) as 库存总量 from book;
3.统计平均库存
select avg(stock) as 平均库存 from book;
4.查询最大库存和最小库存
SELECT MAX(stock) AS 最大库存,
MIN(stock) AS 最小库存
FROM book;
5.按出版社统计图书数量,并筛选数量大于等于1的出版社
SELECT press, COUNT(*) AS 图书数量
FROM book
GROUP BY press
HAVING COUNT(*) >= 1;
8.多表连接查询
1.等值连接:查询读者姓名和所借图书
SELECT reader.rname, book.bname
FROM reader, borrow, book
WHERE reader.rno = borrow.rno
AND borrow.bno = book.bno;
2.内连接:查询借阅记录详情
SELECT r.rname, b.bname, br.borrowdate, br.returndate
FROM borrow br
JOIN reader r ON br.rno = r.rno
JOIN book b ON br.bno = b.bno;
3.左外连接:查询所有读者及其借阅记录
SELECT r.rno, r.rname, br.borrowid, br.bno
FROM reader r
LEFT JOIN borrow br
ON r.rno = br.rno;
4.右外连接:查询所有借阅记录及读者信息
SELECT r.rname, br.borrowid, br.bno
FROM reader r
RIGHT JOIN borrow br
ON r.rno = br.rno;
5.自身连接:查询同一年出版的不同图书
SELECT a.bname AS 图书1,
b.bname AS 图书2,
a.pubyear AS 出版年份
FROM book a
JOIN book b
ON a.pubyear = b.pubyear
AND a.bno < b.bno;
9.嵌套查询
1.IN:查询借过书的读者
SELECT *
FROM reader
WHERE rno IN(
SELECT rno FROM borrow
);
2.比较运算符:查询库存大于平均库存的图书
SELECT bno,bname,stock
FROM book
WHERE stock > (
SELECT AVG(stock)
FROM book
);
3.EXISTS:查询存在借阅记录的读者
SELECT *
FROM reader r
WHERE EXISTS(
SELECT *
FROM borrow br
WHERE br.rno = r.rno
);
4.ANY:查询库存大于任意一本2018年出版图书库存的图书
SELECT bno,bname,stock
FROM book
WHERE stock > ANY(
SELECT stock
FROM book
WHERE pubyear = 2018
);
5.ALL:查询库存不少于所有图书库存的图书
SELECT bno,bname,stock
FROM book
WHERE stock >= ALL(
SELECT stock
FROM book
);
10.集合查询
1.UNION:合并读者姓名和作者姓名并去重
SELECT rname AS name
FROM reader
UNION
SELECT author AS name
FROM book;
2.UNION ALL:合并读者姓名和作者姓名,不去重
SELECT rname AS name
FROM reader
UNION ALL
SELECT author AS name
FROM book;
3.模拟 INTERSECT:查询借过书的读者证号
SELECT rno
FROM reader
WHERE rno IN(
SELECT rno
FROM borrow
);
4.模拟 EXCEPT:查询没有借书的读者
SELECT rno,rname
FROM reader
WHERE rno NOT IN(
SELECT rno
FROM borrow
);
11.视图操作
1.创建未归还图书视图
CREATE VIEW v_unreturned AS
SELECT br.borrowid, r.rname, b.bname,
br.borrowdate, br.returndate
FROM borrow br
JOIN reader r ON br.rno = r.rno
JOIN book b ON br.bno = b.bno
WHERE br.actualdate IS NULL;
2.查询视图
SELECT * FROM v_unreturned;
3.创建学生读者视图,带 WITH CHECK OPTION
CREATE VIEW v_student_reader AS
SELECT *
FROM reader
WHERE dept LIKE '%学院%'
WITH CHECK OPTION;
4.测试可更新视图
UPDATE v_student_reader
SET phone = '18800000000'
WHERE rno = 'R2024002';
说明:
该视图基于单表 reader 创建,没有聚合函数、分组和连接,因此属于可更新视图。
5.创建不可更新的聚合视图
CREATE VIEW v_press_count AS
SELECT press, COUNT(*) AS total
FROM book
GROUP BY press;
说明:
该视图使用了 GROUP BY 和 COUNT 聚合函数,因此一般不能直接更新。
6.删除视图
DROP VIEW v_unreturned;
DROP VIEW v_student_reader;
DROP VIEW v_press_count;
12.索引
1.创建普通索引
CREATE INDEX idx_book_bname
ON book(bname);
2.创建唯一索引
CREATE UNIQUE INDEX idx_reader_phone
ON reader(phone);
3.创建复合索引
CREATE INDEX idx_borrow_rno_bno
ON borrow(rno,bno);
查看索引
SHOW INDEX FROM book;
SHOW INDEX FROM reader;
SHOW INDEX FROM borrow;
删除索引
DROP INDEX idx_book_bname ON book;
DROP INDEX idx_reader_phone ON reader;
DROP INDEX idx_borrow_rno_bno ON borrow;
13.数据完整性约束验证与级联操作
1.实体完整性验证:主键不能为空
INSERT INTO book(bno,bname,author,press,pubyear,stock)
VALUES(NULL,'测试书','测试作者','测试出版社',2020,1);
说明:
bno 是 book 表主键,主键不能为空,因此插入失败。
2.参照完整性验证:插入不存在的图书编号
INSERT INTO borrow(rno,bno,borrowdate,returndate,actualdate)
VALUES('R2024001','B999','2025-05-01','2025-05-15',NULL);
说明:
B999 在 book 表中不存在,borrow.bno 是外键,因此违反参照完整性。
3.用户定义完整性验证:库存不能小于0
UPDATE book
SET stock = -1
WHERE bno = 'B001';
说明:
stock 字段设置了 CHECK(stock>=0),库存不能为负数。
4.用户定义完整性验证:库存不能小于0
UPDATE book
SET stock = -1
WHERE bno = 'B001';
说明:
stock 字段设置了 CHECK(stock>=0),库存不能为负数。
5.ON UPDATE CASCADE 测试
UPDATE reader
SET rno = 'R2024999'
WHERE rno = 'R2024003';
SELECT * FROM borrow
WHERE rno = 'R2024999';
说明:
reader.rno 更新后,borrow 表中对应的外键 rno 会自动同步更新。
14.触发器
准备:删除旧触发器
DROP TRIGGER IF EXISTS trg_borrow_before_insert;
DROP TRIGGER IF EXISTS trg_return_after_update;
DROP TRIGGER IF EXISTS trg_reader_before_delete;
创建删除读者日志表
CREATE TABLE reader_delete_log(
logid INT AUTO_INCREMENT PRIMARY KEY,
rno CHAR(12),
rname VARCHAR(20),
dept VARCHAR(40),
delete_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
1.借书时自动减少库存
DELIMITER //
CREATE TRIGGER trg_borrow_before_insert
BEFORE INSERT ON borrow
FOR EACH ROW
BEGIN
DECLARE v_stock INT;
SELECT stock INTO v_stock
FROM book
WHERE bno = NEW.bno;
IF v_stock <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '库存不足,不能借书';
ELSE
UPDATE book
SET stock = stock - 1
WHERE bno = NEW.bno;
END IF;
END //
DELIMITER ;
测试:
SELECT stock FROM book WHERE bno='B003';
INSERT INTO borrow(rno,bno,borrowdate,returndate,actualdate)
VALUES('R2024002','B003','2025-05-01','2025-05-15',NULL);
SELECT stock FROM book WHERE bno='B003';
2.还书时自动增加库存并计算罚款
DELIMITER //
CREATE TRIGGER trg_return_after_update
AFTER UPDATE ON borrow
FOR EACH ROW
BEGIN
IF OLD.actualdate IS NULL AND NEW.actualdate IS NOT NULL THEN
UPDATE book
SET stock = stock + 1
WHERE bno = NEW.bno;
UPDATE borrow
SET fine =
CASE
WHEN NEW.actualdate > NEW.returndate
THEN DATEDIFF(NEW.actualdate, NEW.returndate) * 1
ELSE 0
END
WHERE borrowid = NEW.borrowid;
END IF;
END //
DELIMITER ;
测试:
UPDATE borrow
SET actualdate = '2025-05-18'
WHERE rno='R2024002'
AND bno='B003'
AND borrowdate='2025-05-01';
SELECT * FROM borrow
WHERE rno='R2024002'
AND bno='B003';
SELECT stock FROM book WHERE bno='B003';
3.删除读者前记录日志
DELIMITER //
CREATE TRIGGER trg_reader_before_delete
BEFORE DELETE ON reader
FOR EACH ROW
BEGIN
INSERT INTO reader_delete_log(rno,rname,dept)
VALUES(OLD.rno, OLD.rname, OLD.dept);
END //
DELIMITER ;
测试:
INSERT INTO reader
VALUES('R2024888','日志测试','男','测试学院','无');
DELETE FROM reader
WHERE rno='R2024888';
SELECT * FROM reader_delete_log;
15.数据安全性
1.创建用户并授予查询权限
CREATE USER 'lib_user1'@'localhost'
IDENTIFIED BY '123456';
GRANT SELECT
ON library_db.book
TO 'lib_user1'@'localhost';
查看权限:
SHOW GRANTS FOR 'lib_user1'@'localhost';
2.授予权限并允许转授权
GRANT SELECT, INSERT
ON library_db.reader
TO 'lib_user1'@'localhost'
WITH GRANT OPTION;
说明:
WITH GRANT OPTION 表示该用户可以将自己拥有的权限再授予其他用户。
3.回收权限
REVOKE INSERT
ON library_db.reader
FROM 'lib_user1'@'localhost';
4.创建角色并授权
CREATE ROLE 'reader_role';
GRANT SELECT
ON library_db.*
TO 'reader_role';
CREATE USER 'lib_user2'@'localhost'
IDENTIFIED BY '123456';
GRANT 'reader_role'
TO 'lib_user2'@'localhost';
SET DEFAULT ROLE 'reader_role'
TO 'lib_user2'@'localhost';
16.视图与派生表
1.带聚合函数的视图:统计每个读者借阅次数
CREATE VIEW v_reader_borrow_count AS
SELECT r.rno, r.rname, COUNT(br.borrowid) AS borrow_count
FROM reader r
LEFT JOIN borrow br
ON r.rno = br.rno
GROUP BY r.rno, r.rname;
查询:
SELECT * FROM v_reader_borrow_count;
2.派生表:查询借阅次数大于1的读者
SELECT *
FROM (
SELECT rno, COUNT(*) AS borrow_count
FROM borrow
GROUP BY rno
) AS t
WHERE t.borrow_count > 1;
3.WITH 子句统计每位读者借阅数量
WITH reader_count AS (
SELECT rno, COUNT(*) AS total
FROM borrow
GROUP BY rno
)
SELECT r.rno, r.rname, rc.total
FROM reader r
LEFT JOIN reader_count rc
ON r.rno = rc.rno;
4.WITH 子句:查询未归还图书
WITH unreturned AS (
SELECT br.borrowid, r.rname, b.bname,
br.borrowdate, br.returndate
FROM borrow br
JOIN reader r ON br.rno = r.rno
JOIN book b ON br.bno = b.bno
WHERE br.actualdate IS NULL
)
SELECT * FROM unreturned;
17.扩展思考
- 如何处理分类实体的自关联?
分类实体存在“上级分类”和“下级分类”的关系,因此可以在 category 表中设置 parentid 字段。
category(
cid,
cname,
parentid
)
其中 cid 是分类编号,parentid 是上级分类编号,同时 parentid 也是指向 category.cid 的外键。
如果某分类没有上级分类,则 parentid 可以为空。
这种设计可以表示树形结构,例如:
图书
├── 计算机
│ ├── 数据库
│ └── 网络技术
└── 文学
└── 科幻小说
2. 借阅关系是否可能存在数据冗余?
借阅表如果只保存 rno、bno、borrowdate、returndate、actualdate 等信息,则冗余较少。
但是如果在 borrow 表中同时保存读者姓名 rname、图书名称 bname、出版社 press 等信息,就会产生数据冗余。
例如:
borrow(borrowid, rno, rname, bno, bname, borrowdate)
其中 rname 依赖 rno,bname 依赖 bno,而不是直接依赖 borrowid,会产生传递依赖。
规范化方案:
将读者信息保存在 reader 表;
将图书信息保存在 book 表;
borrow 表只保存读者证号和图书编号作为外键。
3. 如何防止库存为负数?
可以从两个方面防止库存为负数:
第一,在 book 表中设置 CHECK(stock >= 0) 约束,保证库存不能小于0。
第二,在借书触发器中判断库存数量。
当插入 borrow 借阅记录时,先查询图书库存。
如果库存小于等于0,则使用 SIGNAL 抛出错误,阻止借书操作;
如果库存大于0,则允许借阅,并自动将库存减1。
这样可以保证借书时不会出现库存为负数的情况。
4. 预约图书功能需要增加哪些实体和关系?
如果系统支持预约图书功能,可以增加 reservation 预约表。
reservation(
reserveid,
rno,
bno,
reservedate,
status
)
其中:
reserveid 表示预约编号;
rno 是读者证号,外键引用 reader.rno;
bno 是图书编号,外键引用 book.bno;
reservedate 表示预约日期;
status 表示预约状态,如“预约中”“已取消”“已完成”。
新增关系:
一个读者可以预约多本图书;
一本图书可以被多个读者预约;
读者和图书之间通过 reservation 表形成 m:n 联系。
5. 为什么遵循最小权限原则?
最小权限原则是指只给用户完成工作所必需的权限,不授予多余权限。
在本实验中,如果某用户只负责查询图书信息,则只授予 SELECT 权限即可,不应该授予 INSERT、UPDATE、DELETE 等权限。
例如:
GRANT SELECT ON library_db.book TO 'lib_user1'@'localhost';
这样可以防止用户误删、误改数据,提高数据库安全性。
如果后续不再需要某项权限,还可以使用 REVOKE 回收权限。
6. 并发借阅库存仅剩1本的图书会产生什么问题?如何解决?
当多用户同时借阅同一本库存仅剩1本的图书时,可能出现超卖问题。
例如两个用户同时读取库存 stock=1,都认为可以借阅,然后同时插入借阅记录并执行库存减1,最终可能导致库存异常或出现超额借阅。
解决方法:
1. 使用事务保证借书操作的原子性;
2. 使用 SELECT ... FOR UPDATE 对图书库存记录加行级锁;
3. 在触发器中判断库存是否大于0;
4. 给 stock 字段设置 CHECK(stock >= 0) 约束。
示例:
START TRANSACTION;
SELECT stock
FROM book
WHERE bno='B001'
FOR UPDATE;
UPDATE book
SET stock = stock - 1
WHERE bno='B001'
AND stock > 0;
INSERT INTO borrow(rno,bno,borrowdate,returndate)
VALUES('R2024001','B001','2025-05-01','2025-05-15');
COMMIT;
这样可以避免多个用户同时修改库存导致数据不一致。
实验总结
通过本次图书借阅管理系统综合实验,我系统地完成了数据库设计与实现的全过程。实验从需求分析开始,逐步完成 E-R 图设计、关系模式转换、物理表结构设计、数据插入、查询操作、视图、索引、完整性约束、触发器和权限管理等内容,使我对数据库应用系统的设计流程有了更加完整的认识。
在实验过程中,我首先根据图书馆业务需求分析出图书、读者、借阅、出版社、分类等实体,并明确了它们之间的联系类型。例如,出版社与图书之间是一对多关系,分类与图书之间是一对多关系,读者与图书之间通过借阅表形成多对多关系。通过 E-R 图设计,我进一步理解了概念结构设计在数据库开发中的重要作用。
在逻辑结构设计阶段,我将 E-R 图转换为关系模式,并分析各表是否满足第三范式。通过规范化设计,避免了在借阅表中重复保存读者姓名、图书名称等信息,减少了数据冗余,提高了数据一致性。在物理设计阶段,我使用 SQL 创建数据库和数据表,设置了主键、外键、非空、默认值和 CHECK 约束,对实体完整性、参照完整性和用户定义完整性有了更深刻的理解。
在数据操作部分,我练习了 INSERT、UPDATE、DELETE 和 SELECT 语句,并完成了基础查询、聚合查询、多表连接查询、嵌套查询和集合查询等内容。通过这些练习,我掌握了 SQL 查询语句的常见写法,尤其是多表连接和嵌套查询的使用方法。实验中还创建了视图和索引,理解了视图可以简化复杂查询,索引可以提高查询效率,但也会增加维护成本。
在触发器部分,我实现了借书自动减少库存、还书自动增加库存并计算罚款、删除读者前记录日志等功能。通过触发器实验,我认识到数据库不仅可以存储数据,还可以在特定操作发生时自动执行一些业务逻辑。实验过程中也遇到了一些问题,例如外键约束导致数据无法删除、CHECK 约束插入失败、触发器语法错误等。通过查看错误信息、修改 SQL 语句和反复测试,最终解决了这些问题。
通过本次实验,我不仅巩固了数据库理论知识,也提高了 SQL 编写能力和数据库调试能力。今后在设计数据库系统